Eric Schwartz
2/15/2006 6:00:00 PM
"James B. Byrne" <ByrneJB@Harte-Lyne.ca> writes:
> I have written my first live ruby script that actually performs useful
> work as an exercise in syntax discovery. It is very much a straight
> line utility that could just as easily be written in bash. What I would
> like is for someone to look at the code reproduced below and guide me
> through the steps to convert this into something approximating the ruby
> OOP idiom. I come from a 4GL universe working on mid-range and high end
> iron and OS rather than unix/linux and OOP. So, I have a major
> challenge getting my head out of first gear.
OOP is a different mindset. But given that you work on big iron, you
might have an advantage that you don't know about. I find that when I
describe OOP to people unfamiliar with it, it's easiest to understand
if you start with the data, and add the methods later. Given that
your universe revolves around data, this might not be so hard.
> Looking at this I wondered what can be placed into separate classes. It
> seems to me that the check for environment variables and defaluts and
> then creating the resulting directory strings and regexp objects is
> either one functional group or two, depending if one considers the
> search for environmental settings totally separate from the directory
> validation process or not. I tend to see two classes; GetDirs and
> CheckDirs.
Here, your focus is on what your code is *doing*. Think, instead,
about what it *is*. That is, what data are you collecting, and what
are you doing with it? I would argue that instead what you need is a
Configuration object that stores data like, "What directories should I
be fetching from which machines, and where should I put them?", and
give that object methods like validate_directories() and so on.
Then think of what other things you want to store data about. Say,
your machines. You could define a Machine class that stored generic
data, such as its name, location, and other generic info. You then
could subclass that into HP3000, ZOS_box, and so on. Each of those
would contain methods like get_file(), which would fetch a file from
that machine to the local directory, and maybe reboot(), which would
annoy everybody else using that machine. ;-)
A very loose description might be something like this:
class Configuration
def initialize()
# set all variables to their default values,
# overriding with ENV vars as required.
end
def validate_directories()
# ensure all directories you rely on are present
end
end
class Machine
def intialize(name, location)
# set generic info about Machine here
end
end
class HP3000
def initialize(name, location)
super(); # let Machine handle this
end
def get_file(filename, destination)
# do stuff to put filename on the hp3000 into
# destination
end
end
Notice again how my focus is on the nouns (what things *are*) as
opposed to the verbs (what things *do*). Also notice that the verbs
are attached to the thing that does them in most cases. You could
make an argument that get_file doesn't belong with the HP3000, that it
should be its own top-level subroutine. My reason for placing it
there is this: there is probably a different procedure for each type
of machine you deal with for getting files off of it. By putting the
get_file() method in the machine-specific class, you are putting all
the machine class-specific information in one place, instead of
scattering it all around.
My two major principles when breaking things down are:
* What is the data?
* What does it do?
(In that order)
> My difficulty is that I do not see how all this hangs together after the
> classes and methods are written. Does the script end up as a ruby
> module?
A module is a chunk of code you want to re-use elsewhere. If this
script is supposed to be invoked on its own, then no, you don't want
to make it a module. You may, however, want to put the various
classes you create into their own files so you can re-use them
elsewhere.
> Then what do I do? Do I have a "main" procedure below the class
> definitions in the module?
Any code you do not put in a method or other limited scope gets
executed in the order you see it. So basically, there is no main(),
you just start doing stuff (much as in a shell script).
> I would really appreciate it if someone could invest the time to
> walk me through this because once I see how to map what I presently
> do to the ruby way I expect that many things I am unsure about will
> suddenly become clear. I am particularly interested in how to redo
> this so that Test::Unit can be used to drive it for testing.
I'm in a bit of a rush right now, but maybe someone else will explain
how a method such as I proposed makes it easy to test using
Test::Unit-- otherwise, I'll get back to you later and try and explain
it.
-=Eric