I was trying to follow this DevOps Toolbox tutorial
but I am running across some issues that seem like version
conflicts. The instructions just say to create a Gemfile that
specifies vagrant and veewee with no version numbers. This implies
that the tutorial was written when vagrant was still primarily
distributed as a gem. But, as detailed in
my previous post, I have Vagrant 1.2.2 installed as a
stand alone program. I tried just omitting vagrant from my gem file,
but the gem version got installed anyway because veewee 0.3.7 lists
“vagrant >= 0.9” as a runtime dependency.
I poked around trying to figure out if it was possible to use veewee
with the newer version of vagrant. The
Veewee Readme on Github
indicates that I should probably clone from the repository rather than
using the veewee gem. So I cloned veewee into the top level of my
devops_toolbox directory (which gave me commit
4cf8acc7507d646f897dcd40bf06b2bcc961a6c0). Then I created a Gemfile
that referenced this checkout and then ran ‘bundle install’ in my
devops_toolbox gemset.
The Veewee repository comes with a .rvmrc file which tries to help by
setting ‘bundle exec’ aliases for veewee and irb. Not sure that helps
and it leads to conflicts with the .rvmrc file in my devops_toolbox
parent directory. I tried removing it but then when I ran ‘bundle
install’ in devops_toolbox, I got the following error:
The
tutorial
says to use ‘vagrant basebox templates’ but vagrant no longer supplies
the templates. But I can now get the same list from veewee. Fedora 19
just came out at the beginning of the month and comes with Ruby
2.0.0. I am tempted but having already had version conflict problems
getting this far, I think I am going to play it safe and build a
Fedora 18 box with Ruby 1.9.3. I am not sure if I am going to end up
with p327 (which is what originally shipped with Fedora 18) or if the
install will automatically install p448 from the updates. We’ll see.
So I want to get started with Vagrant again. So before getting
started, I updated my VirtualBox install to 4.2.12. My Ubuntu VM still
works just fine. So now I can move on to Vagrant.
Installing Vagrant
The last time I played with Vagrant it was distributed as a Ruby gem
but apparently that is now deprecated. Following the instructions on
the Getting Started Guide
I first found and removed old copies of vagrant that I had. All of
them seemed to be installed in a ruby 1.9.3 patchset that I didn’t
have any more, so I just deleted the gem directories for that
ruby.
While I was looking at my rubies and gemsets, I went ahead and updated
the gems in my default ruby’s global gemset. I was a little surprised
that I have a minitest gem installed (Doesn’t minitest ship with ruby
now?) and that I appear to have quite an old version (Aug 27, 2011
according to the version list on rubygems.org). Curiouser and
curiouser.
Anyway, back to Vagrant. I downloaded the dmg for v1.2.1 and ran the
installer. I see a Vagrant folder in my Applications folder - and
/usr/bin/vagrant is available and shows the expected version number.
Making a VM
I have some other VirtualBox VMs installed in ‘/Users/cnk/VirtualBox VMs’
So it seems logical to add more there. I have a kind of mix of stuff
in that directory so at some point I need to go back and figure out
what pieces go with what other pieces. For now, let’s make a new
directory for my new box, and initialize vagrant:
To create a VM I need a ‘box’ to use as a base image. The default in
the Vagrant docs is precise32 which is an Ubuntu image. I may have to
learn to like Ubuntu since last time I looked, the base VirtualBox
images available for Ubuntu were a LOT smaller than the RHEL/CentOS
ones I am more familiar with. ‘vagrant box list’ actually turns up a
lucid32 image from ???? Apparently vagrant stores the boxes in
~/.vagrant.d/ by default. I see that directory - with a boxes/lucid32
subdirectory. However, I also have a directory, ~/.vagrant. From the
moddates and boxes listed, this is from the first time I played around
with Vagrant (for setting up sandboxes to run Brakeman in). If I ever
need those images again, I may need to figure out if I can just move
them. But for now, I am just leaving them alone.
I am a little disappointed that vagrantup.com doesn’t have a
semi-authoritative list of available boxes. The list at
http://www.vagrantbox.es/ seems to still be being maintained. But I
think I’ll start off by just downloading the 32 bit Ubuntu Precise
Pangolin image from VagrantUp:
I edited my edx/Vagrantfile to reference the precise32 image and then
did ‘vagrant up’. It booted - pretty quickly. I can ssh in with
‘vagrant ssh’ and then turn the box back off with ‘vagrant
halt’. That’s enough for now. Tomorrow, pick puppet or chef and create
a working python environment.
Tomorrow Tim and I start a month-long Erlang class from
Erlang Solutions The class notes
say you should come with Erlang installed but the easy option for
installing (brew install erlang) would have given me R14 (and R16 is
just about out) and the
official instructions on how to install from source
were a little off putting. I really don’t want to use MacPorts if I
don’t have to. We use it at work and it’s dependency management is a
bit messy so you end up installing the entire universe to get the one
tool you want.
Fortunately the Erlang Solutions folks have a DMG installer for Erlang
R16A at:
https://www.erlang-solutions.com/downloads/download-erlang-otp.
It says it is compiled for Snow Leopard (10.6.8) but the DMG installer
ran fine and the little bit of playing around I did in the Erlang shell
seems to work.
I was very pleased that the Getting Started
docs mentioned that Erlang ships with a set of tools - including an
Erlang mode for Emacs.
The binary intaller I used placed the lib files in
/usr/local/lib/erlang/ rather than /usr/local/otp but with that small
change, I now have emacs with Erlang support!!
Time to use ctags (or really etags in emacs). Most of the time I work
on projects that are small enough that I can keep a lot of what I need
to know in my head - or look up information from my frameworks on the
internet. However, I am currently working on some code that I am
having trouble sorting through. All of it is home grown - written by
someone who no longer works with us - so no way to find documentation.
Parts of the code are quite clear and have been pretty easy to modify,
but I am having trouble tracing the larger flow of data and
messages. So perhaps the answer is a tool that would make it easier to
go forward and back (particularly back) within the code.
It looks like I have ctags, however, a bunch of tutorials I have
looked at suggest that one should use exuberant ctags instead of just
plain ctags. May as well give that a try. The instructions in
this blog post
say you can just use brew install ctags-exuberant. That worked fine
until the symlink stage when brew said I already had something at
/usr/local/bin/ctags. Looks like ctags is one of the things that
installed when Homebrew installed Emacs 24.1 for me. I might want to
go back to that version, so I changed the name of that symlink to
ctags-from-emacs and then reran the linking step. Now I see:
And, more importantly, when I run ctags --list-languages I see Ruby
in the list of supported languages, which I didn’t see in the ctags
version that came with emacs.
I went looking for advice on how to generate my TAGS file and found
mactag which will let you set up a
configuration file to tell ctags which things you want indexed (your
code, gems, etc) and where to find them. Looks pretty useful.
I have been looking forward to reading Sandi Metz’s
“Practical Object-Oriented Design In Ruby”
since I heard she was writing it. The
LA Ruby Study Group has chosen it as our
next book, so I’ll have some folks to discuss it with. But I still want
record some ideas I have been struggling with as I read.
First, I am surprised that Sandi manages to be so thought-provoking
with such concise examples. Chapters 2 and 3 revolve around a code
example that contains about 50 lines of code. But she still manages to
create several plausible alternative implementations, each with it’s
own advantages and faults. Her examples remind me of problems I have
run into in other code. More importantly, the book offers ideas for
for refactoring such messes - but with the following caution against
over-engineering:
Do not feel compelled to make design decisions prematurely. … When
the future cost of doing nothing is the same as the current cost,
postpone the decision. Make the decision only when you must with the
information you have at the time.
Chapters 2 & 3 - Constructing Objects
Depend on behavior, not data.
Concretely this usually amounts to accessing data/attributes via their
getters (and setters). At first that seems a bit high-ceremony for
Ruby - but being Ruby, it really isn’t. If you don’t need the getter
to do anything special, you can create it with attr_reader
:blah. 99% of the time the result of the method #blah is just going
to be @blah. When when you find something in that last 1%, it is great
that the only refactoring you need to do is to define a more complex
getter #blah.
If you have some data that needs to travel together but isn’t really
enough to warrant its own class (yet), then use a ruby Struct to
make bundle it up - with named attributes to make its meaning clearer.
Reactor to reveal intent
The book is filled with gems like this one - after a section
demonstrating several very small refactoring:
Do these refactoring even when you don’t know the ultimate
design. They are needed, not because the design is clear, but
because it isn’t. You do not have to know where you’re going to use
good design practices to get there. Good practices reveal design.
Isolate dependencies
One of the best ways to reduce coupling between classes is though
dependency injection. Where possible, pass in the things you depend on
as parameters. One immediate pay off for this is that it makes your
testing easier. Instead of using mocks and stubs to intercept method
calls while running your tests, you can just pass in an appropriately
constructed fake that provides just enough support so you can write
your tests. For example, if the current test depends on data from the
class you are depending on, instead of passing in the entire object,
pass in a Struct containing the data you need for the current test.
Sometimes it isn’t feasible to refactor to use dependency injection.
When your code already has some issues with tight coupling, you may
not be able to fully extract a hidden object right away - or you may
not be able to change the class’s initialization signature without
breaking a ton of other things. So the book shows examples of using a
wrapper to initialize your object using the interface you wish you had -
or of isolating the methods that are making you wish you had a
separate, dependent object so they are ready to extract when you can (p 32).
Sandi also showed an example of creating a method in your class whose
entire purpose is to wrap a call made on a dependent object. This can
be particularly useful if that dependent object is in active
development and frequently changes its method signatures - or if you
are afraid that call to the external dependency will be overlooked
within a much larger method (p 50).
Using hashes for initialization (and merging them with a hash of
default attributes) is very useful. It frees you from trying to recall
a order for the initialization parameters and helps instance creation
code serve as some of the documentation about what the object contains.
Chapter 4 - Creating Flexible Interfaces
Once your object has a single responsibility, then you need to work on
giving it an optimal interface.
Object-oriented applications are defined by the messages that pass
between objects.
This chapter focuses on how to determine if your messages are right:
are you sending the right messages? and are you sending them to the
right receiver? On the sending side, the message should specify what
it wants, not how the receiving object should behave. If the sender
is doing a lot of micro-management, then perhaps the sender needs to
fully delegate to the receiver. If the receiver does not have all the
knowledge to take care of the delegated request, that may be a sign
that you need some other intermediate object that manages the request.
Context
The things that Trip knows about other objects make up its
context…. The context that an object expects has a direct effect
on how difficult it is to reuse…. Objects that have a complicated
context are hard to use and hard to test; they require complicated
setup before they can do anything.
I recognize the complicated setup code smell but I hadn’t explicity
thought about having a lot of context in terms of an object knowing
too much about it’s collaborators. Does your class make a bunch of
calls to methods in other objects? If so, even if you have minimized
coupling by using dependency injection, your object knows the names of
many methods in its collaborators - and may need to know a lot about
the parameters for those methods. The second refactoring of this
chapter (fig 4.7 on p 72) gives an example of how to reduce what a
trip needs to know about its collaborator, the mechanic. Instead of
handing the mechanic individual bicycles and asking him to prepare
them, the trip just tells the mechanic to make the preparations it
needs to make for this trip. This is how you move to specifying what
you want done, not how you want it done - but increasing the trust
with which one object delegates to another.
The examples in the book are great, but I do have one question about
the example on p 72, figure 4.7. Doesn’t passing the trip instance
along to the mechanic as the argument to the prepare method
potentially increase the coupling between the trip and mechanic
classes? Not really - it merely changes which object is in
control. One of the two classes needs to know that they collaborate
around preparing bicycles. In the initial code, the trip knows about
bicycles and it knows that the mechanic needs to prepare them. In the
final example, the mechanic knows it is responsible for preparing
bicycles and asks the trip to hand them over. The point of the trip
passing ‘self’ when calling the mechanic’s prepare method is 1) it is
a form of dependency injection that facilitates isolated testing and
2) if sometime later the mechanic’s preparations change to need more
information from my_trip than just the list of bicycles, then we don’t
have to add additional parameters to my_trip’s call to
my_mechanic#prepare. When I first saw that it felt like the mechanic
instance suddenly had a much closer relationship with EVERYTHING about
a trip, but in practice, my_mechanic could always have queried my_trip
for that information anyway using my_trip’s public interface. Passing
the trip instance into my_mechanic encourages the mechanic class to
access what ever information it needs from my_trip via that injected
dependency.
Perhaps I am so wowed by POODR because it seems to anticipate the
exact difficulties I have. The very next section, “Trusting Other
Objects”, directly addresses my unease with example 4.7 and points out
that now what trip is full delegating the bicycle preparations to the
mechanic, you could use the same strategy to delegate different
preparations to other classes - using the exact same interface. For
example, you could loop over an array of collaborators and call
prepare(self) on each.
This blind trust is a keystone of object-oriented design. It allows
objects to collaborate without binding themselves to context and is
necessary in any application that expects to grow and change.
So I guess the answer is that I just must get comfortable with this
design paradigm, sometimes summarized as “Don’t ask, tell”.
Law of Demeter
The last section of the chapter discusses how to fix long message
chains (Law of Demeter violations) using a message passing
perspective. Long method chains are problematic because they tie your
object to specific public methods of several other objects. This
increases the chances that your object may need to change because of
changes in a distant object.
The train wrecks of Demeter violations are clues that there are
objects whose public interfaces are lacking.
Instead of using the existing public interfaces of the intermediate
objects to construct these long chains, you need to figure out what
additional public interfaces you need.
Focusing on messages reveals object that might otherwise be
overlooked. When messages are trusting and ask for what the sender
wants instead of telling the receiver how to behave, objects
naturally evolve public interfaces that are flexible and reusable in
novel and unexpected ways.
Chapter 5 - Duck Typing
Methods that check kind_of? or responds_to? before sending a
message are both indications that your object doesn’t trust its
collaborators to do the right thing. When you see this, you know you
are a missing an abstraction which would unify your
collaborators. When you have discovered this abstraction, sometimes it
is sufficient to add a single method to each of the collaborators. In
Sandi’s example, each collaborator class got a prepare_trip method
in which their part of the trip preparations could be defined. Then
instead of trip micro-managing the preparations, it can just call
prepare_trip on each of its collaborators and let them take care of it.
Chapter 6 - Acquiring Behavior Through Inheritance
In Ruby you can affect an object’s method lookup tree (aka inheritance
hierarchy) in a couple of ways. You can create a Class -> SubClass
relationship. You can use extend and include to add modules. Or
you may add methods to a class’s Singleton class. There are some
differences (e.g. you can not create an instance of a module, only a
class) but to a first order approximation, these three things are the
same. All of them add methods which can be found automatically by your
object. If you set up these inheritance relationships correctly,
that’s great. But done incorrectly it’s a recipe for unexpected
failures. Fortunately Sandi provides some great advice on how to stay
out of trouble.
First, how do you know you need subclasses? One clue is often having a
variable called type or category and methods that check the value
of that variable to decide what to do. Sandi’s first piece of advice
is to take note of this sign - but to wait until your category or list
gets a third member before refactoring to use inheritance. Having more
examples makes it easier for you to figure out what behavior should be
in the parent class and what is specific to the subclasses. When you
have enough information to create your class hierarchy, create the
super class as an empty class and have your existing class inherit
from it. Then start fleshing out your other subclasses. Any time your
second subclass needs a method (or version of a method) that is in
your original class (now considered your first subclass), refactor the
method to move the shared behavior up to the superclass. If you are
rigorous about only moving abstract behavior up into the superclass,
you avoid much unnecessary overriding of methods to work around an
imperfect abstraction in your superclass.
Template Method Pattern
One thing that often differs between different subclasses are the
defaults; in the example in chapter 6, road bikes and mountain bikes
have different default tire sizes. So each subclass will need to have
a default_tire_size method with a different value. In addition, it is
important that the parent class also have a default_tire_size method -
even if all it does is raise a NotImplementedError. This is important
so that any additional bike types you create will immediately
implement the shared Bicycle behavior.
classBicycledefdefault_tire_sizeraiseNotImplementedError,"Instances of #{self.class} cannot respond to default_tire_size"endendirb>RecumbentBicycle.new.default_tire_sizeNotImplementedError:InstancesofRecumbentBicyclecannotrespondto: 'default_tire_size'
If RecumbentBicycle is a Bicycle, then by some perspectives the two
classes are by definition tightly coupled. But you should still employ
techniques to spare your subclass from having to know details of how
its superclasses implement methods it wants to extend. A class’s
initialize method is one that subclasses often need to override. And
a common mistake is to forget to call super at the appropriate point
in your subclass’s initialize method.
…forcing a subclass to know how to interact with its abstract
superclass causes many problems. It pushes knowledge of the algorithm
down into the subclasses, forcing each to explicitly send super to
participate. It causes duplication of code across subclasses,
requiring that all send super in exactly the same places. And it
raises the chance that future programmers will create errors when
writing new subclasses, because programmers can be relied upon to
include the correct specializations but can easily forget to send
super.
Hook Messages
One way around the super problem is for the superclass to send hook
messages at appropriate integration points. If a subclass needs to add
or modify the behavior of the superclass, it can implement an
appropriate hook method. As noted above, the superclass must always
have an implementation any shared methods; though usually the
superclass’s hook method is just a no-op.
A similar pattern when dealing with shared and specialized data is to
have the shared attributes defined in the parent class, e.g. in the
spares method in the book’s example. Then each subclass overrides the
method to add additional attributes. Again this can be an invitation
to forget to merge the shared data from the parent class with your
specializations. A safer pattern is for the parent class to manage the
shared data - and to manage melding in the specialized data from each
subclass. In our spares example, the parent class declares the
spares method with all the shared information. Then it calls
local_spares to get any additional data and merges it into the
method’s output. So instead of declaring it’s own spares method,
the subclass adds specialized data by implementing local_spares.