You are here

Archive for No name

Practicing TDD

I have been rereading Thomas Limoncelli's Time Management for System Administrators. One of the points Limoncelli makes is: "[P]sychologists tell us that it takes 21 days of doing a new behavior to develop it into a habit." Give or take a few days, of course.

Test-driven development (TDD) is something I keep revisiting. I have Kent Beck's book on the subject and have read it at least twice. I have tried doing TDD on at least four separate occasions but I seem to stop usually a few hours or a day into the process.

Mark Mzyk wrote of having similar issues with TDD. I disagree with his statement that the issue with TDD is a lack of experience. Instead, I think it has to do with a lack of practice. If you don't do it consistently for any length of time, say three weeks, you're not going to adhere to it. If you follow it, even if not deliberately, you eventually find yourself, as Peter Harkins put it, "drifting into test-driven development."

I think it's kind of like learning to touch-type properly. It's painful and difficult and slow now because I lack experience. As I said before, I spend time actively thinking about which finger should be used to press which key. However, even just a few days into it, I find I spend less time thinking about it.

"How can I practice TDD?" I ask myself. Well, my big thing recently has been problems from Project Euler. I can use it there!

Most of the problems from Project Euler provide a sample test case. For example, problem 1 states:

If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.

Find the sum of all the multiples of 3 or 5 below 1000.

From this, I know that we are solving for the sum of all multiples of 3 or 5 below 1000. We also know that the sum of all such multiples below 10 is 23. This means that I can set up a test script:

  1. require 'test/unit'
  2. require 'euler1'
  3.  
  4. class TestEuler1 << Test::Unit::TestCase
  5.   def test_euler1
  6.     assert_equal( 23, euler1( 10 ) )
  7.   end
  8. end

Running it complains that it can't find a file for euler1. After touching euler1.rb, it complains that there is no method euler1. And after each new error, more changes are made until, eventually, euler1.rb looks like:

  1. def euler1( n )
  2.   23
  3. end

and the test passes. This obviously won't answer the question correctly but doing this proves that the unit testing framework works. It also proves that it considers 23 to be equal to 23 which indicates that there are no silly math errors in the language interpreter, at least at this point. From here, I know I can implement an algorithm and as long as it meets the given criteria, the test should pass. If I'm uncertain about this, I can find other points to test as well. The values for any given n up to 58 are listed in sequence A126592 at The On-Line Encyclopedia of Integer Sequences.

Since this process appears to work for one problem, it should work for all of them. I'll try it and see how it goes.

Touch-typing

My dirty secret (well, not secret much longer) is that I don't touch-type in a traditional sense. I do type reasonably fast and I do type with more than two fingers but it's not touch-typing. I don't follow the standard home row method that is taught in typing classes everywhere.

I've had plenty of chances to learn how to touch type and, to be honest, I squandered them. I'm certain that some of my teachers throughout middle and high school would love to hear that, fifteen to twenty years too late.

The issues I have with my typing, compared with normal touch-typing, is that I have a comparatively lower (but not significantly so) accuracy rate and sometimes I find myself looking at the keyboard.

Jeff Atwood states "We are typists first, programmers second." This is true for system administrators as well, although perhaps less so for those who work solely in a Windows environment.

I used to say "Well, I type well enough, right?" However, Steve Yegge says "If you have two hands, then 70 wpm, error-free, is easily within your reach." And while I might have 70+ wpm, I'm certainly not error-free.

What most people don't realize is that typing errors cost. As if to underline my point, I made an error when typing that sentence. I originally typed "that typiung er" and then I noticed, backspaced back, fixed it to say "that typing" and went on with the rest of the sentence. Fixing errors breaks flow and costs keystrokes. It cost six keystrokes to backspace and remove the error and then five keystrokes to retype what I had erased. (I could have kept the cost to eight keystrokes if I'd been paying attention: Left arrow 6, delete 1, end 1.) Eleven keystrokes is more than a word and possibly might be counted as two by most wpm counters.

If I can improve, it behooves me to do so.

Since I was waiting for something to finish running in the background, I decided to practice for a bit. I loaded the TI-99/4A emulator and started the Touch Typing Tutor cartridge. Touch-typing has not changed significantly over the past thirty years so I figured this should work as well as anything else.

What surprised me most going through even the basic exercises is that I had to think about which key to press. Being asked to type 's' made me have to remember which finger was over the 's' key. As it went on, I was hard-pressed to keep myself from reverting back to my existing habits just so I could finish it. By the end of the second sublesson, my hands hurt although that could have been a posture issue.

Tonight, I'll do the same thing again. Until I'm no longer thinking about which finger to use, I'll keep doing it these sublessons. Then I'll do the next one. And maybe, in the end, I'll have this figured out.

Drupal, Subversion, CVS, and Piston

When I decided to start this blog, I decided to maintain the site almost exclusively in subversion. Having worked with this for a few weeks, I've found it to be much easier than the "traditional" method of setting up Drupal.

I'm not the only one to do this. Agaric Design's approach was helpful but left me scratching my head.

To do this, first, I created an area for Drupal in the vendor directory of my repository: svn mkdir drupal/5

I use a vendor directory because I have some projects that, while unrelated, rely on the same software. I could use a vendor branch in each project but I decided that more overhead than I wanted. (If I had a single repository per project, vendor branches would be fine. Instead, since I have multiple projects in a repository, a separate vendor directory makes more sense.)

(For those wondering, the 5 denotes the version of Drupal. Versions 5 and 6 are incompatible so it's necessary to separate the Drural code and modules by major version.)

Before I can import Drupal core into my repository, I need to check it out from Drupal's CVS. So in the directory I created and, following the directions, I run:

cvs -z6 -d:pserver:anonymous:anonymous@cvs.drupal.org:/cvs/drupal co -r DRUPAL-5--14 drupal

Now, to add it to the repository:

svn add drupal
svn commit

To import a module, the steps are mostly the same but using the different CVS parameters: (the globalredirect module used as an example)

cvs -z6 -d:pserver:anonymous:anonymous@cvs.drupal.org:/cvs/drupal-contrib checkout -r DRUPAL-5--1-4 -d globalredirect contributions/modules/globalredirect
svn add globalredirect
svn commit

Now, to add this into the site, create a repository for the site. I use 'web' as the main directory of the site and I want to install Drupal in that directory. So in the repository, I run:

svn update
piston import http://svn.ithiriel.com/svn/vendor/drupal/5/drupal ./web

Piston is a tool that synchronizes including code from other repositories or locations. It works like svn:externals but allows for local modifications and tracking those. This is useful since each Drupal site has different configuration files.

Piston requires the local directory to be at the latest revision. In almost all cases, this requires running svn update before running Piston.

To install a module, say the globalredirect module from before, I go to web/sites/all/modules and check it out in the same fashion:

svn update
piston import http://svn.ithiriel.com/svn/vendor/drupal/5/globalredirect ./globalredirect

This may seem a lot of work to do at first but I'm pretty sure it pays off the next time you need to update the site.

Since I first started work on the site, Drupal 5.15 was released and a new version of the globalredirect module was released. Both of these needed to be updated.

To update the local copy of the original Drupal source, I go back to the directories created before. In /vendor/drupal/5/drupal, I run:

cvs update -r DRUPAL-5-15 -dP

It's necessary to see if any files have been added or deleted. svn status will tell you if there are any. I add or delete as necessary. When the status checks out, I commit it with svn commit.

Back in the repository for the website, I go to the directory above the web directory and run:

svn update
piston update web
svn commit

Now the upgrade for Drupal core is done. All that's left to do is run the update script. All of that done with very little fuss.

To update the globalredirect module, I go to /vendor/drupal/5/globalredirect and run:

cvs update -r DRUPAL-5--1-5 -dP

As for the core, I see if any files have been added or deleted. When everything is ready, I commit the revision.

To update the module on the site, I go to web/sites/all/modules in the site's repository and run:

svn update
piston update globalredirect
svn commit

As before, I run the update script and it's done.

Upgrading modules isn't such a big deal but upgrading the core tends to feel like a pain. Being able to do it with a set of easy commands makes the process much more tolerable.

There are two issues with this method though.

The first is that when checking directly out of the Drupal CVS, the version numbers will not be set correctly. If using Drupal 6 or the update status module for Drupal 5, the CVS deploy module is required so the version numbers can be determined correctly.

The second is that this process exposes the CVS and .svn directories to the public. However, I can deny access to these with Apache with these directives:

RedirectMatch 404 /\\.svn(/|$)
RedirectMatch 404 /CVS(/|$)

(For those following along, if you do not have access to the Apache configuration file, the documentation for RedirectMatch indicates that it can be placed in .htaccess files. I haven't tried it so I make no guarantees.)

Edit: Made some grammatical and wording changes because I wasn't happy with the originals.

Books

Books are probably my preferred medium of learning. I prefer books to the degree that I chose to look into Ruby last year rather than concentrate on Python because of the availability of good quality Ruby books and a dearth of good Python books.

I know that I prefer books partially because of personal, fluff reasons. I know books. Books have a physical presence. Books are old-fashioned, just like me sometimes.

Books are "offline" resources. If I read something in a book and need to go back to it, I can pick up that book, page to where it was, and reread it. As long as I have physical access to a book, I can find that information at any time, no matter where I am. Computer resources instead rely on the presence of a computer. I tend to forget to bring my aging laptop with me so I can't rely on that. Internet resources rely on being connected to the internet, which is not likely in some circumstances, such as on a train.

Books are "permanent" resources. Barring physical destruction, a book is going to last a reasonably long time. Texts dating back at least a millenium are known to still be usable and readable (assuming you understand the language). Internet resources are, by comparison, "transient." A web page that you look at today may not be available tomorrow. The site maintainer could take the site offline. The author could delete the page. The page could be moved as part of a site reorganization and the maintainer could fail to put redirects in place to send visitors to the new location.

Books are more useful to me than web browsers for accessing many things at once. However, this is likely because I only have a single screen for development. If I had multiple monitors (two, or even Jeff Atwood's three), this may not be as much of an issue.

Books have their drawbacks too. They're heavy so they're not really portable. I think two decent-sized books (thinking the hardback first edition of W. Richard Stevens' Advanced Programming in the UNIX(R) Environment and Martin Fowler's Refactoring: Improving the Design of Existing Code) are heavier than all but the largest laptops. Computers clearly win for information density. (The PDF for the second edition of the Pickaxe book (or, if you prefer, Programming Ruby) is not quite 6 MB. Since you can get light, less-than-a-pound flash drives that hold 8 GB, you could carry over one thousand books on a key chain.)

The search function of books sucks. Unless you know about where the content you want to find is, you have to consult the index and then use that to try to find it. There's no ready indicator of fluff versus desired content. And, in the end, you are at the mercy of whoever created the index. If they did a poor job of making the index, you will encounter difficulties finding what you're looking for. The internet clearly wins here. Google can search a vast repository of human knowledge which easily contains more information than some libraries.

There are some compromises. Several publishers offer their books in eBook or PDF format. This increases portability and, in some cases, search as well. I imagine you could possibly write a local search engine to search PDFs if you can access their content. O'Reilly and Pearson Technology Group created Safari Books Online, a service by which you pay money every month (or year) to get internet access to a large number of books. $42.99/month gets you unlimited access and there are cheaper although limited options. I personally find this service invaluable for what I do but your mileage may vary.

I personally believe that books and computers (including the internet) will eventually balance themselves out and complement each other. In addition to services like Safari Books Online, I expect most of the short-term information, e.g. what goes into books like Python Cookbook, will eventually end up online. A wiki equivalent of O'Reillys Cookbook or Hacks series is where I see that going. The longer-term information, more "language agnostic" topics, e.g. Refactoring or the Gang of Four's Design Patterns will continue to be seen in book format. Books will never completely vanish, not even from the developer culture.

Project Euler

I discovered Project Euler yesterday via a post on the Ruby-Talk mailing list. If you're the sort of person who enjoys puzzles and doesn't mind math, I suggest that you try out the problems for yourself.

I've been working through the problems slowly and in numerical order. While most of my solutions have been "brute force," it has given me an opportunity to learn how to do things in Ruby.

Why slowly? Because I haven't had a lot of time.

Why in numerical order? Later problems build on earlier ones. As I reach a problem that requires a given function or routine, I can go "Hey, I just did that in problem #x" and reuse it.

Why Ruby? Ruby was my "language to learn" last year but I neglected it so I'm hoping I can get more done before I work on this year's language (which will probably be Erlang or Scheme).

A lot of what I've learned to do is quick one-liners with arrays, much like what Drew Olson posted almost two years ago. (I love reinventing the wheel, don't you?) And then there have been neat numerical tricks.

For example, to find out if a number is prime with a somewhat naive algorithm:

  1. class Fixnum
  2.   def prime?
  3.     return false if self < 2
  4.     return false if self == 2
  5.     return false if self % 2 == 0
  6.  
  7.     i = 3
  8.     bound = Math.sqrt( self )
  9.     while i <= bound and i < self
  10.       return false if self % i == 0
  11.       i += 2
  12.     end
  13.  
  14.     true
  15.   end
  16. end

Monkeypatching this into Fixnum is probably not the greatest idea to some people but it allows use of syntax like 31.prime? (useful for testing) and ( n + 1 ).prime?, which is theoretically useful. For example, to find if the Mersenne number for a given power of two is prime, you could write: ( 2 ** n - 1 ).prime?

And, most importantly, I find n.prime? to read better than prime?( n ). Readability always trumps cleverness. Luckily, here, it's both readable and clever.

If you want to learn how to use a language better, I suggest you use it for the Project Euler problems. After a while, you'll find ways to do things you've probably not thought of before (or, possibly, ever needed to do before). And for Ruby fans, you can find a use for Array#each_cons.

How I Got Started Programming

It's an old meme but it still offers insight, I think.

How old were you when you started programming?

Eight.

Before then, I had entered in programs from magazines (they had such things back in the early 1980's) but I had not actually endeavored to try to create something of my own. What I did create was a colossal din of ugly graphics and noise but it was something I actually created.

How did you get started programming

I grew up around computers. We had several TI-99/4A computers.

My father was a programmer as well but, to this day, I'm not sure if he did it as part of his day job or if he did it as a hobby. I think it was mostly his influence that kept me working with computers and programming. I don't know if that was deliberate or not.

What was your first language?

BASIC.

What was the first real program you wrote?

This depends on your definition of "real."

My first "serious" program that I remember was a program for playing Yahtzee in TI BASIC.

Discounting that, the next would be a BattleMech "database" project I did in Turbo Pascal 5.0. Exhibiting a lot of bad programming practices, it at least worked for what I needed it to do. However, there were issues and I was planning on rewriting it from scratch. I never quite got there.

What languages have you used since you started programming?

Trying to be chronologically accurate: BASIC, Pascal, C, sh, C++, Perl, Java, Python, SQL, PHP, VBScript, Cold Fusion, Ruby, Lua

Points of contention are:
Is LOGO a language?
Do different dialects of BASIC qualify as different languages?

What was your first professional programming gig?

I did a very small amount of Java during my internship with IBM.

If there is one thing you learned along the way that you would tell new developers, what would it be?

Always ask "Why?" If someone tells you that x is a best practice, find out why it is before blindly using it.

What's the most fun you've ever had programming?

Rewriting the dice rolling routine for my modular Python IRC bot. Different games have different dice rolling requirements so I ended up making the dice routine itself modular too. The first draft of that rewrite was done on notebook paper early one morning when I didn't have computer access.

Basic Item representation in Ruby

  1. class Item
  2.   attr_accessor :id, :name, :icon, :quality, :bonding, :class_id, :equip_inventory_type,
Articles: 
Source code: 

XML Parser: Rendering the Item in Ruby

The first language to be used for XML parsing is Ruby. Ruby appears to have three main XML libraries: REXML, Hpricot, and libxml-ruby. As time allows, each will be tested.

Before starting the testing, several things need to be done. First, there needs to be a representation of the Item class in Ruby. From the pseudocode initially shown in the problem statement, I derive the Ruby representation of the Item class and its helper classes.

Since no limitations have been set for accessing or modifying the object's data, all fields are implemented with attr_accessor. Unfortunately, since attr_accessor does not have the ability to set default values, the variable initialization needs to be done in the constructor.

One notable difference between the pseudocode and the Ruby version is the casing. While the pseudocode uses "camel case" variable names as standard for Java, the Ruby version uses "underscore" variable names as per the conventions of the community. This creates small differences like critRating vs. crit_rating. There is an ongoing debate as to which is easier to read.

This represents the bare minimum thus far needed to implement the Item object. When discussing testing, other methods will be added to the Item class and helper classes.

About

About Ithiriel

Ithiriel is my blog for computer-related things, primarily programming and system administration. It helps me keep track of what I am trying to do, demonstrate what I have learned, and hopefully provide information useful to someone else (and, if possible, the community at large).

Content is licensed under the Creative Commons Attribution 3.0 Unported License. Code provided by me on this site, unless otherwise stated, is licensed under a BSD-like license.

Licenses

Unless otherwise noted, all content posted by Chris Ess (hereafter "me") is licensed under the Creative Commons Attribution 3.0 Unported License.

Unless otherwise noted, all code copyrighted by me on this site is released under the following license terms:

Pages