Wednesday, December 29, 2010

Empowering Delegation

My buddy, Tom Looy, shared this article with me:
http://www.babusinesslife.com/Tools/Features/The-art-of-action-.html
In it, Stephen Bungay provides a historical basis for suggesting that delegation by communicating the intention (as opposed to dictating approach and tactic) is a far more powerful way to delegate.

Bungay notes:
  • you cannot anticipate the unforeseeable; to attempt this false precision can result in demoralization, original guidance becoming misguidance;
  • as delegation occurs, each level should add only the amount of detail they can actually provide, no more;
  • prefer delegation in person;
  • at each delegation, the superior verifies by requesting an action plan from the subordinate.
  • delegation comes in the form of Auftrag: a task and a purpose; Bungay refers to this as "direct opportunism"
Mission Command

The essences of this approach:

  • vision is shared from top-to-bottom; alignment of strategy
  • action is contextualized; tactics are appropriate for the situation
  • all levels enjoy an appropriate autonomy (and therefore have a chance to enjoy a sense of ownership)

He suggests the following practices:

  • decide what really matters -- use what knowledge is available to you (experience + known circumstances); don't extend plans beyond what you can clearly see.
  • get the message across -- hand down goals and rationale; then request a plan.
  • give people space and support -- set boundaries that prevent disaster and allow all else.
Observations
  • This is a fine way of expressing the tension between autonomy and alignment.
  • This approach assumes that you can get alignment of strategy.  That you can achieve a shared vision.
    • Do this in part by not just handing down strategy, but contextualize as much as is feasible.
  • You have to really figure out how to express the right boundaries:
    • can a developer introduce a new architecture component?
    • can an architect decide how scalable the site should be?
  • This approach gets complicated when the subordinate is less experienced (usually the case?); this is how the organization develops and learns.
    • this is the nuts and bolts of growth: can you mentor/coach without taking over?
    • perhaps this is the practical way of identifying boundaries; initially leave it open to anything, have regular reviews and adjust.
      • include some guidance as to when a review should happen immediately? (e.g. when changing a fundamental approach or introducing a new technique or changing a project process)
(todo: include an example)

Tuesday, December 14, 2010

How git saved my bacon today...

Okay, so that title is a little dramatic, but it sure felt amazing to use my version control system to pull me out of a hole.

The story so far...
I had been working my way through the excellent text Agile Web Development with Rails (4th edition) by Sam Ruby.

I let myself be led by the nose through a couple of working sessions without running all of the functional tests.  Suddenly, there I was on Iteration F4 and my test complained:


$ rake test
.
.
.


Started
.F....................
Finished in 0.538259 seconds.


  1) Failure:
test_should_destroy_cart(CartsControllerTest) [test/functional/carts_controller_test.rb:43]:
"Cart.count" didn't change by -1.
<1> expected but was
<2>.

However, this particular test failure wasn't described in the text (oh noes!).  I was on my own to troubleshoot the underlying issue.  I tried a number of typical first-line troubleshooting techniques: sprinkling a few pretty-prints here and there to get a sense for why the destroy wasn't actually destroying.  But to no avail.  And since I hadn't run my functional tests in some time, I didn't know when I introduced the defect. :-/ 

git bisect to the rescue!
Luckily, I had been diligent at least about committing my work to a local git repo.  I was committing each "iteration" (unit of work from the text), so I had a reasonably high-fidelity history.

With git bisect, I was able to quickly hunt down the offending commit.

First I went back in time a bunch of commits (about 8 commits back) and ran the rake test.  They succeeded!  So, at least I know the tests were running back then and branched there as "last_known_good":

(master) $ git branch -f last_known_good 4586dadff6cf838262c48149ca265e5226cb80b1
(master) $ git co last_known_good
(master) $ rake test
Started
...............
Finished in 0.399094 seconds.

15 tests, 24 assertions, 0 failures, 0 errors, 0 skips

I then initialized a bisect session:

(master) $ git bisect start
(master) $ git bisect bad
(master) $ git bisect good last_known_good
Bisecting: 3 revisions left to test after this (roughly 2 steps)
[8aab0db8f45e8a0c2f5ffc10e6f2382d90c249e8] (E1,E2) Added 'quantity' to Line Items

This first step is the halfway point between the "last_known_good" branch and my master branch (hence the name: git is bisecting the history, doing a binary search until the actual last known good commit (or even better, the first known bad commit) is identified.

Now, it's just a matter of running my test and letting bisect know whether they succeeded or not.  bisect took care of doing the binary search through the history to find the actual last known good:

jonamac:work ((no branch))$ rake test
Started
......................
Finished in 0.541449 seconds.

22 tests, 34 assertions, 0 failures, 0 errors, 0 skips

that one was good.  So, I just tell git bisect that.  It then finds the midpoint commit between this commit (about the 4th from the top)

$ git bisect good
Bisecting: 1 revision left to test after this (roughly 1 step)
[a17b740253ceb4e4df5f055808aa638bee269a7f] (F1) Added partials for Line Item and Cart.

git tells me that he's moved to that midpoint.  I just run my rake test again and tell git about the results.

$ rake test
Started
.F.E...F.EEEE...EEEE..
Finished in 0.557046 seconds.

  1) Failure:
test_should_destroy_cart(CartsControllerTest) [test/functional/carts_controller_test.rb:43]:
"Cart.count" didn't change by -1.
<1> expected but was
<2>.
...
22 tests, 22 assertions, 2 failures, 9 errors, 0 skips


oiy!  this one is bad!  Again, I just tell git that fact and it continues to hunt down the source...

$ git bisect bad
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[bc5470e9380a80448fd61e780d24fe8e1927dec3] (E3) Improved cart display.

...run my test again...

$ rake test
Started
.F....................
Finished in 0.538259 seconds.

  1) Failure:
test_should_destroy_cart(CartsControllerTest) [test/functional/carts_controller_test.rb:43]:
"Cart.count" didn't change by -1.
<1> expected but was
<2>.

22 tests, 33 assertions, 1 failures, 0 errors, 0 skips

and as I tell git about this last one (that I see the failure), it reports that it has found the first bad commit:

$ git bisect bad
bc5470e9380a80448fd61e780d24fe8e1927dec3 is the first bad commit
commit bc5470e9380a80448fd61e780d24fe8e1927dec3
Author: John S. Ryan
Date:   Sun Dec 12 18:00:54 2010 -0800

    (E3) Improved cart display.
    
    + Added ability to empty cart.

There we go!  Something in this commit is the root cause of my issue.  gitk can be helpful to visualize what happened:


It was bisect, not I who applied those tags that start with "bisect/good" and "bisect/bad".  This is handy to get a sense for what just happened.

Now I've got my smoking gun.

What's left is to simply check out at the actual last known good and apply the diff from the next commit, one logical chunk at a time.  Turns out, the bug was a typo (I wrote "card" instead of "cart").

Post Mortem
There was a deep cause to my problem: I had not run my test suite before I committed (repeatedly) and thus injected a defect that was there for some time.  Had I stuck to my guns and continued to run my tests, I would have found this bug much faster.

That said, it's nice to have a safety net.  Lucky for me, I was diligent about my use of my DVCS.  With git's bisect, I was able to track down the source of the defect pretty quick in a rather reliable process.


Sunday, October 17, 2010

Running Things on Multiple Macs

I use Things as my core Trusted System in my personal GTD.  I wanted to use it on both my work computer and my personal machine.  At the time of writing, Culture Code is planning on providing Mac-to-Mac sync.  In the meantime, there's a reasonable workaround.

The basic idea is to put your Things database on a "Disk in the Cloud".  You can use any such service.  While I am using iDisk (comes with MobileMe), I'm current transitioning off that onto Dropbox.  I found incomplete instructions online (including Culture Code's FAQ).  Here's a step-by-step walkthrough.

WARNING: According to Culture Code's FAQ on this matter, you should NOT share a Things database where one machine is running Tiger (10.4) and another is running Leopard (10.5) or better.

WARNING: Make sure you take a backup of your Things database (at the very least, run a Time Machine backup) before you begin.

WARNING: Cultured Code does NOT support you running both instances of Things at the same time. Follow my instructions precisely and you'll not have a problem. Be aware, though, that it's not clear what damage might be caused if you open the Things database from both computers.


Step 1: Install Things on Both Computers

If you're like me, you already have Things installed on one computer.  You'll just have to install it on the second computer.  Cultured Code proudly offers a no-nonsense license allowing you to run the application on multiple computers by the same person.

On your second computer:

  1. Download Things from Cultured Code: http://culturedcode.com/things/

    It downloads as a ZIP file which your Mac will automatically unzip; the file that ultimately lands is Things.app.


  2. Copy Things.app to your /Applications folder.

    Just like you would to install any Mac application.

  3. Run Things.app to initialize it.

    You're allowing this second installation of Things to create all the folders and empty database.

  4. Add your License to this installation!

    It is important that you initialize this copy of Things with your license info. That data will NOT be synced across machines. To add your license, in the "Things" pull-down menu, select "License..." and enter your license information.

  5. Quit Things

    You're about to move the database for Things; best to quit the application before you do.

Step 2: Move the Things Database to a Shared Drive

Now, we'll make the database from our primary Things installation become our shared database by moving it onto the shared drive.  (My instructions assume you're using Dropbox, but again, it doesn't matter; just adjust your paths to point to the right location for your setup).

On your first computer:

  1. Make sure Things is NOT running.


  2. Create a folder on your shared drive to be the new home for your shared Things database.

    I'll just follow the directory structure we see for where apps store their per-user configuration in my set-up.  You can place this folder anywhere you want.  It must be on your shared drive.

    For iDisk users, the base directory is 


    /Volumes/iDisk

    and for Dropbox users, by default your Dropbox folder is located at:

    ~/Dropbox

    creating the directory that will be the home of your Things database:

    $ mkdir "~/Dropbox/Library/Application Support/Cultured Code/" (don't forget the quotes)

  3. Now, move your Things Database to this new location:


    $ mv "~/Library/Application Support/Cultured Code/Things" \
    "~/Dropbox/Library/Application Support/Cultured Code/"
    (quotes are required!)

  4. Now, we'll create a soft link from to where Things thinks the database to where you've just moved it:

    $ cd "~/Library/Application Support/Cultured Code/"
    $ ln -s "~/Dropbox/Library/Application Support/Cultured Code/Things" Things

  5. Let's make sure we're doing good so far. Launch Things on the first computer to make sure all is still well (you can launch it however you normally do, I'll show you a command-line way).

    $ open -a Things

    If your Things launched normally and you can still see all your data, you're good so far. If not, stop here and restore from a backup (and consider not doing this).

  6. Quit Things.

On your Second Computer


I'm assuming that by now your Shared Disk service has sync'ed the Things files to the second computer. This generally is pretty quick. Dropbox uses Growl alerts (the bubble-like notifications in the top right corner) to let you know of changes to your shared folder. Just make sure that you've got a green check mark on your Dropbox icon in the status bar. Once your shared folder is synced, you're ready to use it on the second computer.

  1. Make sure Things is NOT running.

    If you're running Things while you modify the location of the database, you risk corrupting the database.

  2. Delete the empty Things database.


    $ rm -r "~/Library/Application Support/Cultured Code/Things"

  3. Link the Things database from the shared drive:


    $ cd "~/Library/Application Support/Cultured Code/"
    $ ln -s "~/Dropbox/Library/Application Support/Cultured Code/Things" Things

    Now, you have both computers soft-linking to the shared folder.

  4. Fire-up Things, here, to ensure that you're now able to read the shared database on this computer as well.

    If you see all your data, you've done it. There's one more step, but grats, the sharing has started.
  5. Quit Things.

Write a Script to Launch Things from a Lock File


Technically, you could stop here. But doing so leaves you vulnerable to having Things running on both computers at the same time (which will undoubtedly, eventually, cause you to lose data if not corrupt the Things database). I'm going to provide you with a script, that when run ensures that Things isn't running on another computer, first.

On the First Computer


  1. Create the script "Things.sh" on the Shared Drive.

    In your favorite text editor create the file named "Things.sh" and save it to the Shared Drive (since everyone has TextEdit, I'm including instructions using that editor; any will do).

    $ cd "~/Dropbox/Library/Application Support/Cultured Code/"
    $ touch Things.sh
    $ open -a TextEdit Things.sh

    In this script include the following (I recommend you copy and paste instead of keying this all in yourself):

    #/bin/bash
    
    
    acquire_lock() {
      DID_ACQUIRE_LOCK=1
      MY_KEY=`hostname`
      if [ -f "$LOCK_FILE" ]; then
        CURRENT_KEY=`cat "$LOCK_FILE"`
    
        # If I possess the key, this is my lock file (perhaps Things terminated abnormally?)
        if [ "$MY_KEY" == "$CURRENT_KEY" ]; then
          DID_ACQUIRE_LOCK=0
        fi
      else
        echo $MY_KEY >"$LOCK_FILE"
        DID_ACQUIRE_LOCK=0
      fi 
    
      return $DID_ACQUIRE_LOCK
    }
    
    release_lock() {
      if [ -f "$LOCK_FILE" ]; then
        rm "$LOCK_FILE"
      fi
    }
    
    report_already_locked() {
      # prefer using Growl (installed with Dropbox)
      if [ -f /opt/local/bin/growlnotify ]; then
        growlnotify -s -a Things.app -t "Can not start (lock file exists)" \
                    -m "Things is already running on another computer.  Quit Things on that computer, first. \
    Or, if you know what you are doing, delete the lock file at: \
    $LOCK_FILE"
      else
        # Otherwise, use the built-in text-to-speech
          say -v Alex "Things is already running on another computer.  Quit Things on that computer, first."
      fi
    }
    
    # Customize this path to match a path on the shared drive:
    LOCK_DIR="$HOME/Dropbox/Library/Application Support/Cultured Code"
    LOCK_FILE="$LOCK_DIR/Things.lock"
    
    # Here's where all the action starts...
    if (acquire_lock); then
      open -a Things -W
      release_lock
    else
      report_already_locked
    fi
    
  2. Save the file.
  3. Set the script to be "executable" on both computers.

    On BOTH computers run this command:


    $ chmod +x "~/Dropbox/Library/Application Support/Cultured Code/Things.sh"


  4. Launch Things from the shell script.

    Now, every time you want to start-up Things, fire-up this script.