Blog

Commanding objects toward immutability

by Jim

Following the rules for East-oriented Code helps me organize behavior in my code but it can lead to other benefits as well. As a result of following the rules, I find that my code is better prepared for restrictions like that which immutable objects introduce.

I recently went looking for samples of how people are using instance_eval and instance_exec and ended up with a great example from FactoryGirl thanks to Joshua Clayton. As I was searching, I came upon some code which happened to use instance_eval. Although it was a simple use case for that method it lent itself as a much better example of commands, immutability, and East-oriented code.

Here’s the details…

If we want to use nested blocks to create a tree structure, we might create some pseudo-code like this to illustrate our desired code:

Node.new('root') do
  node('branch') do
    node('leaf')
    node('leaf2')
    node('leaf3')
  end
end

The representation of this set of objects should look something like this:

"['root', ['branch', ['leaf', 'leaf2', 'leaf3']]]"

This shows that the created tree is a pair of a named node and an array of named children (who can also have children).

Imperative approach

A simple solution is to initialize a Node and, using an imperative approach, to change its state; that is to say that we alter its collection of children.

class Node
  def initialize(name, &block)
    @name = name
    @children = []

    instance_eval(&block) if block
  end

  attr_reader :children, :name

  def node(name, &block)
    children << Node.new(name, &block)
  end
end

When each node is created, its collection of children is set to an empty array. With each call to the node method, a new Node object is created and shoveled into the collection of children.

If we refactor our sample to inline the methods and show us exactly what’s going on, it would look something like this:

Node.new('root') do
  self.children << Node.new('branch') do
    self.children << Node.new('leaf')
    self.children << Node.new('leaf2')
    self.children << Node.new('leaf3')
  end
end

We can more clearly see what’s happening inside of the node method with this change to our code.

Eastward flow

As I worked with this problem I wondered: what would happen if I started following the 4 rules of East-oriented code?

If our node method returns self, how does that affect our code?

class Node
  # initialize omitted...

  def node(name, &block)
    children << Node.new(name, &block)
    self
  end
end

Fortunately, because our code relies on an imperative approach by changing the state of the children, the code still works.

If we want, we can shrink the space we use by chaining commands together:

t = Node.new("root") do
  node("branch") do
    node("subbranch") do
      node("leaf").node("leaf2").node("leaf3")
    end
  end
end

I think that’s actually a little more difficult to read, so we can go back to the regular style:

node("leaf")
node("leaf2")
node("leaf3")

When seeing techniques like returning self to encourage an East-oriented approach, it’s easy to fixate on the chaining. But it’s commands that we want to introduce, not chaining. The chaining is incidental here.

If you do chain your method calls together, it at least appears more clearly that each subsequent method is operating on the return value of the last one.

If we want to be clear that we’re operating on the last return value, we can maintain the readability of the multiline option by writing it like this:

node("leaf").
node("leaf2").
node("leaf3")

Each line chains the next by adding the dot character. We don’t have a specific need to do this, but it’s good to know how it works.

Not much has changed after introducing our East-oriented approach. We’re still updating that collection of children.

Introducing immutability

What will we see if we introduce immutable objects to our solution?

Immutable objects might just help us make our code more predictable. An object which never changes, of course, stays the same. This allows you to better handle the behavior of the system and, without changing any objects, makes a multithreaded approach much less likely to introduce headaches.

The simplest way to add immutability is to freeze objects as they are initialized:

class Node
  def initialize(name, &block)
    @name = name.freeze
    @children = [].freeze

    instance_eval(&block) if block
  end

  attr_reader :children, :name

  def node(name, &block)
    children << Node.new(name, &block).freeze
    self
  end
end

This, of course, breaks everything. Our code relies upon the fact that the children array may be mutated. Instead of doing the mutation, we’ll see this:

RuntimeError: can't modify frozen Array

Now what?

If we can’t alter the collection, we’re left at creating an entirely new one.

One thing we could do is change the constructor to accept a collection of children when the Node is initialized. Instead of altering the children, we’d use a constructor like this Node.new(name, chlidren). Here’s what that looks like:

class Node
  def initialize(name, children=[], &block)
    @name = name.freeze
    @children = children.freeze

    instance_eval(&block) if block
  end
  # ... omitted code

end

That still doesn’t allow us to change anything until we also change the way our node method works (since it is responsible for handling changes to the children).

If the node method created a new Node instead of altering the children, that would get us what we want. Let’s break it down.

First, when the node method is called, it needs to create the node to be added to the collection of children:

def node(name, &block)
  new_child = Node.new(name, &block)
  # ... ?
  self
end

Since we’re trying to avoid mutating the state of this object, we don’t want to just shove the new node into the collection of children (and we can’t because we used freeze on it).

So let’s create an entirely new node, with an entirely new collection of children. In order to do that, we need to ensure that for every existing child object, we creat a corresponding new node.

For each command to the object with node, we’ll get the representation of what the children should be. So let’s build a method to do that:

def next_children
  children.map{|child| Node.new(child.name, child.next_children) }.freeze
end

When we changed our initializer, that allowed us to set the list of children. Our new next_children method relies on that feature and a recursive call to itself to build the collection of children for that new node with Node.new(child.name, child.next_children).

Looking back at our node method we’ll need to break the rules of East-oriented Code. Since we have immutable objects, we’ll return a new node instead of self.

def node(name, &block)
  new_child = Node.new(name, &block)
  Node.new(self.name, next_children + [new_child])
end

But there’s still a problem left. Because we need our initialized object to execute a block and the contstructor new might actually need to return a different object than the one originally created. The call to node inside the block changes the return value from the instance that new creates, to the instance that node creates.

Controlling the constructor

To better handle our immutable objects and the return values from the methods we created, we can alter the way the new method works on our Node class.

Instead of handling a block in the initialize method, we can move it to new.

Here’s the new new method:

def self.new(*args, &block)
  instance = super.freeze
  if block
    instance.instance_eval(&block)
  else
    instance
  end
end

The first step is to call super to get an instance the way Ruby normally creates them (as defined in the super class of Node). Then we freeze it.

If we haven’t provided a block to the new method, we’ll want to return the instance we just created. If we have provided a block, we’ll need to evaluate that block in the context of the instance we just created and return it’s result.

This means that the block can use the node method and whatever is returned by it.

We need to alter the new method this way because we’re not always just returning the instance it creates. Since our objects are frozen, we can’t allow the block to alter their states.

The way new usually works is like this:

def self.new(*args, &block)
  instance = allocate
  instance.send(:initialize, *args, &block)
  return instance
end

You can see the reason that Ruby has you call new on a class but in practice you write your initialize method. This structure ensures that no matter the result of your initialize method, new will always return an instance of the class you’ve used.

We’re bending the rules to allow us to evaluate the given block and return its result, instead of the instance typically created by new.

After that, we can remove the block evaluation from initialize:

def initialize(name, children=[])
  @name = name.freeze
  @children = children.freeze
end

While the method signature (the list of accepted arguments) has changed for initialize, it’s still the same for new: a list of arugments and a block.

Believe it or not, there’s still one more problem to solve.

Operating on values

We looked at how returning self allows you to chain your method calls. Although we’ve broken that rule and are instead returning a new Node object, it’s important to consider that chaining.

Our initial code still doesn’t work quite right and it’s all because we need to think about operating on the return values of our commands and not relying on an imperitive approach to building and changing objects.

First, here’s what our Node class looks like:

class Node
  def self.new(*args, &block)
    instance = super.freeze
    if block
      instance.instance_eval(&block)
    else
      instance
    end
  end

  def initialize(name, children=[])
    @name = name.freeze
    @children = children.freeze
  end

  attr_reader :children, :name

  def node(name, &block)
    new_child = self.class.new(name, &block)
    self.class.new(self.name, next_children + [new_child])
  end

  def next_children
    children.map{|child| self.class.new(child.name, child.next_children) }.freeze
  end

  def inspect
    return %{"#{name}"} if children.empty?
    %{"#{name}", #{children}}
  end
end

We didn’t discuss it, but there’s an inspect method to return either the name of the node if it has no children, or the name and a list of children if it has some.

Here’s what the code to create the tree looks like:

Node.new('root') do
  node('branch') do
    node('leaf')
    node('leaf2')
    node('leaf3')
  end
end

If we assign the result of that to a variable and inspect it we’ll get a surprising result.

t = Node.new('root') do
      node('branch') do
        node('leaf')
        node('leaf2')
        node('leaf3')
      end
    end
puts [t].inspect

The output will only be ["root", ["branch", ["leaf3"]]]

So what happened to the other leaf and leaf2 objects? Why aren’t they there?

Remember that each node call returns a new node. With every node a new result is returned. The node('leaf') returns an object, but node('leaf2') is not a message sent to the object returned by the first. It is a message sent to the node('branch') result.

Each of those calls is returned and forgotten. Here it is annotated:

t = Node.new('root') do
      node('branch') do
        node('leaf') # returned and forgotten
        node('leaf2') # returned and forgotten
        node('leaf3') # returned and used as the final result
      end
    end
puts [t].inspect
#=> ["root", ["branch", ["leaf3"]]]

The answer to this problem is to command each object to do the next thing. We can achieve this by chaining the methods. The result of one method is the object which will receive the next command.

t = Node.new('root') do
      node('branch') do
        node('leaf'). # dot (.) charater added to chain
        node('leaf2'). # executed on the result of the last node
        node('leaf3') # executed on the result of the last node
      end
    end
puts [t].inspect
#=> ["root", ["branch", ["leaf", "leaf2", "leaf3"]]]

An alternative way to look at this is to store the result of each command:

t = Node.new('root') do
      node('branch') do
        branch = node('leaf')
        next_branch = branch.node('leaf2')
        final_branch = next_branch.node('leaf3')
      end
    end
puts [t].inspect
#=> ["root", ["branch", ["leaf", "leaf2", "leaf3"]]]

Following the rules so you know when to break them

What was interesting about this to me was that my code was prepared for the immutable objects when I prepared it to operate on the same one. By structuring my code to return self and send the next message to the result of the last, I was able to change the implementation from an imperative style to a functional style.

Permalink…

Cohesive behaviors with data clumps

by Jim

A good example of how we use context and locality to understand and manage concepts in our code is using a data clump.

A data clump is a collection of two or more bits of information that are consistently used together. You’ll find that your data loses its meaning when you remove items from the clump.

Date ranges are simple examples of how a data clump puts necessary information into context. An example of this is to find out if a question was asked between today and one month ago. If our Question class implements a query method for this:

class Question
  def asked_within?(start_date, end_date)
    (start_date..end_date).cover?(self.asked_date)
  end
end

Then we can pass in our desired dates to get the answer:

# using ActiveSupport
start_date = 1.month.ago
end_date = Time.now
question.asked_within?(start_date, end_date)

Discovering whether a question is within this time frame always requires both a start and end date. This is an indication that we can only understand the feature and indeed only implement it when we have this data clump. To better encapsulate the behavior of these values, we can create a class to manage initializing objects that represent them.

DateRange = Struct.new(:start_date, :end_date)
last_month = DateRange.new(1.month.ago, Time.now)
question.asked_within?(last_month)

We can then change our Question class to instead take a date range object for the asked_within? method, but the question’s responsibilities have grown a bit here. A question doesn’t have anything to do with comparing dates, so we can move the control of that information into the data clump that represents them.

DateRange = Struct.new(:start_date, :end_date) do
  def contains?(date)
    (start_date..end_date).cover?(date)
  end
end

Now, instead of the question managing its date comparison, the date range can do the work.

last_month.contains?(question.date_asked)

By analyzing the individual parts of this date comparison we have to juggle a bit more in our heads. Considering a range as an complete object rather than a collection of parts is simpler and we tend not to think of every individual day within a month when doing a mental comparison. A date range is a small system of interacting parts that we better understand as a broader context.

This example shows us the value not only of separating responsibilities, but of bringing objects together. We get more value by putting details into context than we would have if they remained separate.

Things to note

Struct.new returns a class instance. Inheriting from the result of a new Struct creates an anonymous class in the ancestors of your created class:

[DateRange, #<Class:0x007f885d0be518>, Struct, ...]

Instead of class DateRange < Struct.new; end use DateRange = Struct.new and avoid an anonymous class in the ancestors:

[DateRange, Struct, ...]

Additionaly, be careful with large ranges. If our code used include? instead of cover?, Ruby would initialize a Time object for every time between the beginning and end. As your range grows, the memory needed to calculate the answer will grow too.

Avoid excessive memory and use cover? instead. It will check that your beginning date is less than or equal to the given date, and that the given date is less than or equal to the end date.

This article is an excerpt from my book Clean Ruby

Clean Up Your Code and get a Free chapter of Clean Ruby

If you liked this post and want more like it, get periodic tips in your inbox by leaving your email address below. Clean Ruby

To get more information about cleaning up your objects, classes, and code, then check out Clean Ruby, an ebook which will describe ways to keep your code clean, maintainable, and focused on business value. Make your OOP more obvious, easier to understand, easier to test, and easier to maintain.



Permalink…

The difference between instance_eval and instance_exec

by Jim

There’s an important difference between instance_eval and instance_exec. And there’s a great lesson about how to use them well in FactoryGirl

But first, before you go rushing off to build your fantastic DSL, let’s look at what instance_eval is and does.

The simplest of examples can be taken straight from the Ruby docs:

class KlassWithSecret
  def initialize
    @secret = 99
  end
end
k = KlassWithSecret.new
k.instance_eval { @secret } #=> 99

The current value for self inside the provided block will be the object on which you call instance_eval. So in this case the k object is the current context for the block; @secret is a variable stored inside k and instance_eval opens up access to that object and all of it’s internal variables.

The interface that FactoryGirl provides is simple and straightforward. Here’s an example from it’s “Getting Started” documentation:

FactoryGirl.define do
  factory :user do
    first_name "Kristoff"
    last_name  "Bjorgman"
    admin false
  end
end

Here, FactoryGirl uses instance_eval to execute the blocks of code passed to factory.

Let’s take a look at some representative code from how FactoryGirl makes this work:

def factory(name, &block)
  factory = Factory.new(name)
  factory.instance_eval(&block) if block_given?
  # ... more code
end

That’s not actually the code from FactoryGirl, but it represents roughly what happens. When the method factory is called a new Factory is created and then the block is executed in the context of that object. In other words where you see first_name it’s as if you had that factory instance before it and instead had factory.first_name. By using instance_eval, the users of FactoryGirl don’t need to specify the factory object, it’s implicitly applied to it.

Ok, that’s all well and good, but what about instance_exec?

I’m glad you asked.

The instance_eval method can only evaluate a block (or a string) but that’s it. Need to pass arguments into the block? You’ll be frozen in your tracks.

But instance_exec on the other hand, will evaluate a provide block and allow you to pass arguments to it. Let’s take a look…

FactoryGirl allows you to handle callbacks to perform some action, for example, after the object is created.

FactoryGirl.define do
  factory :user do
    first_name "Kristoff"
    last_name "Bjorgman"
    admin false

    after(:create) do |user, evaluator|
      create_list(:post, evaluator.posts_count, user: user)
    end
  end
end

In this sample, the after(:create) is run after the object is created, but the block accepts two arguments: user and evaluator. The user argument is the user that was created. The evaluator is an object which stores all the values created by the factory.

Let’s take a look at how this is implemented:

def run(instance, evaluator)
  case block.arity
  when 1, -1 then syntax_runner.instance_exec(instance, &block)
  when 2 then syntax_runner.instance_exec(instance, evaluator, &block)
  else        syntax_runner.instance_exec(&block)
  end
end

FactoryGirl will create a callback object named by the argument given to the after method. The callback is created with a name, :create in this case, and with a block of code.

The block that we used in our example had two arguments.

The run method decides how to execute the code from the block.

The callback object stores the provided block and Ruby allows us to check the arity of the block, or in other words, it allows us to check the number of arguments.

When looking at a case statement, it’s a good idea to check the else clause first. This gives you an idea of what will happen if there’s no match for whatever code exists in the when parts.

There we see syntax_runner.instance_exec(&block) and this could easily be changed to use instance_eval instead. Ruby will evaluate, or execute, the block in the context of the syntax_runner object.

If the block’s arity is greater than zero, FactoryGirl needs to provide the objects to the block so that our code works the way we expect.

The second part of the case checks if the block arity is equal to 2.

when 2 then syntax_runner.instance_exec(instance, evaluator, &block)

If it is, the syntax_runner receives the instance (or in our case user) and the evaluator.

If, however, the arity is 1 or -1 then the block will only receive the instance object.

So what is that -1 value? Let’s look at the ways we could create a callback:

# Two arguments and arity of 2
after(:create) do |user, evaluator|
  create_list(:post, evaluator.posts_count, user: user)
end
# One argument and arity of 1
after(:create) do |user|
  create_group(:people, user: user)
end
# Zero arguments and arity of 0
after(:create) do
  puts "Yay!"
end
# Any arguments and arity of -1
after(:create) do |*args|
  puts "The user is #{args.first}"
end

Ruby doesn’t know how many args you’ll give it with *args so it throws up it’s hands and tells you that it’s some strange number: -1.

This is the power of understanding how and when to use instance_exec; users of the DSL will expect it to make sense, and it will.

But wait! There’s more!

What if you want to specify the same value for multiple attributes?

FactoryGirl.define do
  factory :user do
    first_name "Kristoff"
    last_name  "Bjorgman"

    password "12345"
    password_confirmation "12345"
  end
end

In the above example, both the password and password_confirmation are set to the same value. This could be bad. What if you change the password for one, but forget to change the other? If they are inherently tied in their implementation, then that could lead to some unexpected behavior when they are not the same.

I would, and probably you would too, prefer to tell FactoryGirl to just use the value I’d already configured.

Fortunately FactoryGirl allows us to use a great trick in Ruby using the to_proc method. Here’s what it looks like in use:

FactoryGirl.define do
  factory :user do
    first_name "Kristoff"
    last_name  "Bjorgman"

    password "12345"
    password_confirmation &:password
  end
end

The important part is the &:password value provided to password_confirmation. Ruby will see the & character and treat the following as a block by calling to_proc on it. To implement this feature, FactoryGirl defines to_proc on attributes and there will use instance_exec to provide the symbol password to the block:

def to_proc
  block = @block

  -> {
    value = case block.arity
            when 1, -1 then instance_exec(self, &block)
            else instance_exec(&block)
            end
    raise SequenceAbuseError if FactoryGirl::Sequence === value
    value
  }
end

What about lambdas and procs?

Some commenters in Reddit raised an important question about how these methods behave when given lambdas and procs.

If you provide a lambda which accepts no arguments as the block, instance_eval will raise an error:

object = Object.new
argless = ->{ puts "foo" }
object.instance_eval(&argless) #=> ArgumentError: wrong number of arguments (1 for 0)

This error occurs because Ruby will yield the current object to the provided block as self. So you can fix it by providing a lambda which accepts an argument:

args = ->(obj){ puts "foo" }
object.instance_eval(&args) #=> "foo"

This changes a bit if you use instance_exec:

object.instance_exec(&argless) #=> "foo"
object.instance_exec(&args) #=> ArgumentError: wrong number of arguments (0 for 1)
object.instance_exec("some argument", &args) #=> "foo"

Because a proc is less restrictive with argument requirements, it will allow either approach to work without error:

p_argless = proc{ puts "foo" }
object.instance_eval(&p_argless) #=> "foo"

p_args = proc{|obj| puts "foo" }
object.instance_eval(&p_args) #=> "foo"

object.instance_exec(&p_args) #=> "foo"
object.instance_exec(&p_argless) #=> "foo"

Now you know, instance_exec and instance_eval are similar in the way they behave, but you’ll reach for instance_exec if you need to pass variables around.

Announcing Ruby Metaprogramming Masterclass

I’m offering a new online class where I’ll be teaching you how to master metaprogramming in Ruby on April 30th (the day after my birthday!)

I’m keeping the spaces limited to 25 so attendees will be able to talk and ask questions but already over a quarter of the seats are gone. So grab a seat now, before they’re all gone.

Clean Up Your Code and get a Free chapter of Clean Ruby

If you liked this post and want more like it, get periodic tips in your inbox by leaving your email address below. Clean Ruby

To get more information about cleaning up your objects, classes, and code, then check out Clean Ruby, an ebook which will describe ways to keep your code clean, maintainable, and focused on business value. Make your OOP more obvious, easier to understand, easier to test, and easier to maintain.



Permalink…

Locality and Cohesion

by Jim

“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.” — Richard Gabriel

This advice is from Patterns of Software by Richard Gabriel.

Keeping cohesive parts of our system together can help us understand it. By managing locality we can keep cohesive parts together.

It’s easy to see coupling in our code. When one object can’t do it’s job without another, we experience frustration in the face of change. We often think about dependencies in our code, but cohesion is the relatedness of the behaviors and plays an import part in how we organize the ideas to support our domain.

def process_payment(amount)
  gateway.authorize_and_charge(amount) do
    deliver_cart
  end
  logger.info "handling payment: #{amount}"
  logger.info "cart delivered: #{id}"
end

The exact purpose of this completely-made-up code isn’t that important. But we can look at parts of this procedure and extract them into a related method:

def process_payment(amount)
  gateway.authorize_and_charge(amount) do
    deliver_cart
  end
  log_purchase(amount)
end

def log_purchase(amount)
  logger.info "handling payment: #{amount}"
  logger.info "cart delivered: #{id}"
end

As Gabriel points out in his book, we can compress a procedure into a simple phrase like log_purchase but this compression carries a cost. In order to understand the behavior of this log_purchase phrase, we need to understand the context around it.

Indeed, we might look at this and realize that there’s a problem with the way we managed the locality of the procedure. Instead of easily understanding a single method, we might look at process_payment and realize there’s a bit more to it than we first expect.

We’re forced to understand the log_purchase and the context which previously surrounded it’s procedure. A second look at this extraction might lead us to reconsider and to go back to inline the method. Let’s keep this code with a tighter locality:

def process_payment(amount)
  gateway.authorize_and_charge(amount) do
    deliver_cart
  end
  logger.info "handling payment: #{amount}"
  logger.info "cart delivered: #{id}"
end

While extracting the log_purchase method was easy, given the original code, it added a bit too much for us to understand and it doesn’t feel quite right. Handling the locality of this code helps us to better understand it and to make better decisions about how to improve the main process_payment method.

Consider this: How much must you pack into your head before you can begin evaluating a part of your code?

While breaking procedures up into small methods can be a useful way to make easy to understand (and easy to test) parts, we may do so to the detriment of understanding.

This is something to consider if you are building a DSL to compress ideas in your code or if you’re trying to create objects to manage your business logic. I’ll be writing more about the value of controlling the locality of behavior in your system, but I’d love to hear how you manage locality. What do you do to ensure that related bits stay together?

Clean Up Your Code and get a Free chapter of Clean Ruby

If you liked this post and want more like it, get periodic tips in your inbox by leaving your email address below. Clean Ruby

To get more information about cleaning up your objects, classes, and code, then check out Clean Ruby, an ebook which will describe ways to keep your code clean, maintainable, and focused on business value. Make your OOP more obvious, easier to understand, easier to test, and easier to maintain.



Permalink…

The 4 Rules of East-oriented Code: Rule 4

by Jim

Often the rules we create are defined by their exceptions.

It is difficult to create a program which continually passes objects and never returns data. Often the first rule of “Always return self” is met with immediate rejection because it’s easy to see the difficulty you’d encounter if that rule is continually followed for every object.

In my presentation for RubyConf, I showed how we break the rules to allow value objects to handle data for a template. I previously wrote about the approach I used in the presentation to push data into a value object.

class Address
  def display(template)
    if protect_privacy?
      template.display_address(private_version)
    else
      template.display_address(public_version)
    end
    self
  end
end

In the sample above, an Address instance commands a template to display_address with different versions of data: private_version or public_version. This makes a flexible interface that allows Address to create any number of different versions if necessary. Perhaps the requirements will demand a semi_public_version in the future; our design of the template need not change.

This is a great way to break the rules. Value objects allow us to parameterize a collection of data in a single object. The alternative to this approach would be to use setter methods on the template object:

class Address
  def display(template)
    unless protect_privacy?
      template.street = street
      template.apartment = apartment
      template.postal_code = postal_code
    end
    template.city = city
    template.province = province
    template.display_address
    self
  end
end

We can plainly see that although the code follows the rules by commanding the template object, there’s also quite a lot happening in this display method on Address. If the requirements change we might feel encouraged to complicate the unless block or “refactor” it into a case statement. While that might solve our problem, the resulting code could lead to some difficult to read and understand implementation details.

By breaking the rules with a value object we can better encapsulate the ideas in a private address object or public or any other type we desire.

But we’re not just breaking the rules inside the Address methods; the template breaks the rules too. Rule 2 says that objects may query themselves and subsequently means they should not query other objects. But by choosing to break the rules we make a design decision at a specific location to make things better.

No matter what rules you follow, you decide not only to follow them, but decide to break them as well. To make your program easy to understand and to create reasonable expectations, you can lean on creating barriers. Preventing yourself from doing one thing frees you to do another.

Embrace constraints.

How do you add constraints to your programs? What are you better able to do by adding restrictions?

Clean Up Your Code and get a Free chapter of Clean Ruby

If you liked this post and want more like it, get periodic tips in your inbox by leaving your email address below. Clean Ruby

To get more information about cleaning up your objects, classes, and code, then check out Clean Ruby, an ebook which will describe ways to keep your code clean, maintainable, and focused on business value. Make your OOP more obvious, easier to understand, easier to test, and easier to maintain.



Permalink…

The 4 Rules of East-oriented Code: Rule 3

by Jim

When I set out to create my presentation for RubyConf, I wanted to provide the audience with something they could easily try. By doing that, one could walk away and put themselves in a position to think about their code differently. While, James Ladd, the creator of East-oriented Code made some basic rules, I decide to take them and frame it in the specific context of Ruby:

  1. Always return self
  2. Objects may query themselves
  3. Factories are exempt
  4. Break the rules sparingly

After writing about Rule 1 and Rule 2 I’m very eager to get to Rule 3. It’s an easy way to break the intent of this style without breaking the rules.

Factories are Exempt

They must be. If you returned self from Object.new you’d just get Object back, not an instance of an object. So factories are exempt from returning self.

The best way to get around any of these rules is to just make something into a factory. But here lies the danger. It’s important to first think about what these objects are doing. For what are they responsible?

We could create a class to sweep our messy code under the rug.

user = User.new
signup = UserSignupProcess.new
signup.create_with(user)

The code above, we could say, is East-oriented. The factories create instances, and the signup object is told to create_with and given the user object.

Beyond this (inside create_with), it could easily be an enormous mess. While we can and probably should use different programming techniques for different situations, taking a load of if statements and sweeping it into a class could still be problematic.

Now, the sample code above is completely made up to show how you can take part of your program and say “this part is East-oriented, but over here I used this other technique. I call it If-oriented.”

Examining your domain and creating a program to support it requires that you carefully evaluate what objects should exist, what their responsibilities are, and what you will name them.

East-orientation is all about designating responsibilities.

This leads us to breaking the rules…

We’ll be getting to that later. There’s likely very good reasons to break any particular programming rule, but it probably depends on the context.

I wrote Clean Ruby and the chapter on East-oriented Code before I set up the 4 rules for my presentation, but the same lessons are there. I’ll be adding more to it, particularly as discussion and ideas around DCI evolve, but I’m putting effort toward wrapping up the Ruby DSL Handbook. It will soon be complete and the $12 price will go up to $24, so pick it up now if you’re interested.

Ruby DSL Handbook is about how to create a DSL without headaches from metaprogramming and I just released an update with a chapter about creating a DSL without metaprogramming at all. Much like this discussion today, it’s all about managing responsibilities.

Clean Up Your Code and get a Free chapter of Clean Ruby

If you liked this post and want more like it, get periodic tips in your inbox by leaving your email address below. Clean Ruby

To get more information about cleaning up your objects, classes, and code, then check out Clean Ruby, an ebook which will describe ways to keep your code clean, maintainable, and focused on business value. Make your OOP more obvious, easier to understand, easier to test, and easier to maintain.



Permalink…

1999 - 2015 © Saturn Flyer LLC 2321 S. Buchanan St. Arlington, VA 22206

Call Jim Gay at 571 403 0338