tag:blogger.com,1999:blog-65644068230872941132024-03-12T19:23:02.329-07:00Software as a CraftJTiggerhttp://www.blogger.com/profile/09997166268958276999noreply@blogger.comBlogger8125tag:blogger.com,1999:blog-6564406823087294113.post-18038755180833384302011-12-11T19:24:00.000-08:002011-12-11T20:14:16.467-08:00Alfred Extension: Searching EvernoteTonight I wrote an extension for <a href="http://www.alfredapp.com/">Alfred</a> that allows you to easily search <a href="http://evernote.com/">Evernote</a>.<br />
<h1>How To Get It</h1><ol><li>Make sure you've got the <a href="http://www.alfredapp.com/powerpack/">Alfred Powerpack</a> installed (soooooo worth the money and required to use extensions).</li>
<li>Download the extension file: <a href="http://dl.dropbox.com/u/7929259/Search%20Evernote.alfredextension">Search Evernote.alfredextension</a></li>
<li>Simply open that file to install.</li>
</ol><div class="separator" style="clear: both; text-align: left;"><a href="http://dl.dropbox.com/u/7929259/Search%20Evernote.alfredextension"><img border="0" src="http://2.bp.blogspot.com/-SNzNMo-uP9c/TuV-HPP_OyI/AAAAAAAAApU/MQUjBRfJe_Y/s1600/t9vIvL.png" /></a></div><div><br />
</div><h1>How to Use It</h1>The default keyword is "e".<br />
<br />
So: simply fire-up Alfred, type "e" a space and your Evernote search.<br />
<br />
For help with search check out Evernote's knowledge base article, <a href="https://support.evernote.com/link/portal/16051/16058/Article/535/Using-Evernote-s-advanced-search-operators">Using Evernote's advanced search operators</a>.<br />
<br />
<h1>Why Another Search Extension?</h1>Stephen Millard <a href="http://www.thoughtasylum.com/blog/2011/7/8/search-evernote-from-alfred-again.html">wrote an extension for searching Evernote</a>. However it had three behaviors I found undesirable:<br />
<ol><li><b><i>it opens a new window with each and every search.</i></b> It's akin to every search to a website spawning a new window. I prefer to have just one window open, myself. </li>
<li><b><i>it modifies the search query to always be a phrase.</i></b> By default Evernote treats each search word independently; if you want to search for a phrase, surround it with quotes. Millard went out of his way to collapse all search words into a single string. I'm not sure why. Personally I'd rather have the option to do either kind of search.</li>
<li><b><i>it does not bring Evernote to the front.</i></b> I'd rather Evernote pop-up in front, showing the results of my search.</li>
</ol>Technically, all of this is truly unnecessary. For example, you could define an Alfred Global Hotkey to launch Evernote (say, control+alt+⌘+E) and then hit alt+⌘+F to jump to the search box. For me, it's pretty nice having one place where I search.<br />
<h1>How Does It Work?</h1>AppleScript and Evernote's AppleScript API make this task essentially trivial.<br />
<br />
This was my first cut at AppleScript (what a nifty little language!) Here's the source for this simple, simple script:<br />
<br />
<blockquote><hr /><pre>on alfred_script(query)
tell application "Evernote"
activate
if (exists first window) then
set first window's query string to query
else
open collection window with query string query
end if
end tell
end alfred_script
</pre><hr /></blockquote><br />
Enjoy!JTiggerhttp://www.blogger.com/profile/09997166268958276999noreply@blogger.com0tag:blogger.com,1999:blog-6564406823087294113.post-84845386824847307232011-03-14T16:02:00.000-07:002011-03-14T16:45:51.497-07:00Refactoring Trick: Replacing a simple Value with a Value ObjectI've been enjoying James Shore's <a href="http://jamesshore.com/Blog/Lets-Play">Let's Play TDD</a>. It's a fine way to introduce those who are new to the exact mechanics of TDD.<br />
<br />
I wanted to share a trick that Shore uses in Episode 15: "<a href="http://jamesshore.com/Blog/Lets-Play/Episode-15.html">Integrating TaxRate and InterestRate</a>": it's the incremental introduction of a Value Object into a chunk of code that's already using the primitive value.<br />
<br />
In this case, it's a pair of rates: a tax rate and an interest rate.<br />
<br />
The trick he employs is to temporarily provide a casting method that exposes the encapsulated primitive. In this case, it's a rate. For example:<br />
<br />
<pre>package com.jamesshore.finances;
public class InterestRate {
final private double rate;
public InterestRate(int rate) {
this.rate = rate / 100.0;
}
public int interestFor(int amount) {
return (int) (amount * rate);
}
public int rate() {
return (int) rate;
}
}
</pre><br />
Now, everywhere else in the code where you WERE using an integer format of the interest rate, simply include an instance of this class and call <tt>rate()</tt>. Now you can run your tests to green again.<br />
<br />
At this point, you can incrementally push through the use of the Value Object (here, <tt>InterestRate</tt>) until the back-cast (here, <tt>rate()</tt>) is not used anywhere. Nice.<br />
<br />
I did a little research to see if this very trick is covered say in Martin Fowler's canonical work, <a href="http://martinfowler.com/books.html#refactoring">Refactoring</a>. While Fowler <i>does</i> catalog two refactorings in this area: "<a href="http://www.refactoring.com/catalog/replaceDataValueWithObject.html">Replace Data Value with Object</a>" and "<a href="http://www.refactoring.com/catalog/changeValueToReference.html">Change Value to Reference</a>", he does not mention this particular twist of wrist.JTiggerhttp://www.blogger.com/profile/09997166268958276999noreply@blogger.com0tag:blogger.com,1999:blog-6564406823087294113.post-33209893413483372762011-01-12T13:35:00.000-08:002011-02-01T07:52:10.639-08:00Relishing in Perfection Part 3 -- Peeling the Raw Requirements<style>
.paragraph {
padding-bottom: 1em;
}
.quote {
margin-left: 1em;
padding: 1em; 0em; 1em; 0em;
}
.keypoint {
color: #AD9;
font-style: bold;
}
h4 {
text-transform: uppercase;
}
</style><br />
<div class="paragraph">Installment three of "Relishing Perfection", a series on doing BDD with Cucumber, "the right way". If you are new to this series, I recommend you start with <a href="http://software-as-a-craft.blogspot.com/2011/01/relishing-in-perfection-doing-bdd-right.html">the introduction</a>.<br />
</div><h1>Writing Cucumber Features</h1><div class="paragraph">The first step is projecting the general set of requirements into a set of User Stories. In the BDD world fueled by Cucumber, a Feature embodies the bulk of what a User Story is. This work is the essential analysis of the Business Analyst role: distilling hopes, wishes, a nebulous dreams into concrete, actionable goals. The practice of the actual analyst is a topic for another discussion; in this line of thinking, we are going to focus on the actual articulation of user stories (as Cucumber Features) and how that articulation is affected by the fact that Features are executable. The following are some guidelines and specific practices to help guide the authoring portion of this effort.<br />
</div><h5>An Appropriate Nod to Dan North</h5><div class="paragraph">Dan North, the gentleman who coined the term Behavior-Driven Development, has just posted an excellent and more rigorously minded model for looking at the very topic of choosing the right phrasing for Cucumber Features. You will do yourself right by studying his insightful blog entry <a href="http://dannorth.net/2011/01/31/whose-domain-is-it-anyway/">Whose domain is it anyway?</a>. The short short is to minimize the number of domains from which you take language to author your Features. Dan argues (and I see the same thing) that the more domains you add in the more concerns that are included, the more likely something is going to change and thus the more brittle your Features. I discuss the same in more pedestrian terms, in this article; see North for the more rigorous treatment (and for other knowledge-rich nuggets).<br />
</div><h5>The Business Analyst is a Role</h5><div class="paragraph">A quick aside before we dive in, today. While I keep referring to "The Business Analyst", I'm not saying that there must be a separate person on the team who's title starts with a "B" and ends with a "t". I'm saying that there's a mindset/perspective embodied in a role known as "the Business Analyst." So, when I say, "The Business Analyst" I simply mean to say, "whoever is doing this work, with the BA mindset active."<br />
</div><h3>Principles for Writing Cucumber Features</h3><h4>Write Features as if you were the Business Analyst</h4><div class="paragraph">One common mistake when getting started with Cucumber is to include steps that are expressed in terms of underlying technical details. In fact, the Cucumber adaptor for Rails (via the <tt>cucumber-rails</tt> gem.) <em>includes</em> a number of step definitions (within <tt>web_steps.rb</tt>) that specifically call for xpath or CSS selectors. This is a bit unfortunate because to realize the total value of your Features, they need to be expressed in a way that survives changes in underlying implementation details.</div><h5>Don't Be Misled By OOTB Steps</h5><div class="paragraph">This is not to say the Business Analyst should be willfully ignorant of the implementations; quite the contrary. For example, <tt>cucumber-rails</tt>'s <tt>web_steps.rb</tt> comes with a step definition that matches the pattern "Then I am on <page-name>". You might be tempted to include this in your Scenario to verify that clicking on a navigation element brought you to the correct page. However, the implementation of that step simply checks to verify that the URI of the current page matches that of the targeted page. But, if you have forgotten to define a route for that page, the URI will still match, but Rails will render the "Route Error" page. It's more correct to check for, say, the title of the page or some element that would only show up if the correct page was rendered.</div><div class="paragraph">The balance, lies in the perspective of the Business Analyst. Remember, this is the role that must understand both sides of the equation: the requirements as they are motivated by business concerns and the implementation details so far that they influence what's feasible to do with this system. It's from this position that one can write Features that are likely to last: informed by technical details and articulated in terms of elements of the User Interface.</div><h4>Write Features in terms of the visible User Interface</h4><div class="paragraph">A related principle is to articulate the Feature steps in terms of elements of the User Interface (UI). Terms like "buttons", "pull-downs", "title", "navigation bar", etc. are about as technical as one should get without risking losing the Product Owner who's expertise is in the business, not software development.<br />
</div><div class="paragraph">In practice it is helpful to not just agree on what the individual widgets are called, but to also develop an agreed-upon vocabulary for common parts of the page. Common parts include:<br />
<ul><li>Header and Footer</li>
<li>Navigation Bar</li>
<li>Status Message Area</li>
<li>Main Body</li>
</ul>There will also be elements that are repeated across your application. For example, if there is a heavy social networking component to your webapp, perhaps the set of "follow" buttons (e.g. reporting to your onlines friends that you've just bought the hip new new Thing... we'd present a series of buttons for Facebook like/share, Twitter, etc.)... it will facilitate language if you coin a term for that set of social networking controls: "Like Button Bar"? "Social Clicks"? Whatever you choose, it's the job of the Business Analyst to ensure that a shared vocabulary develops.<br />
</div><div class="paragraph">By sticking to the UI, your Features will be both concrete (more easily implementable) and better understood by both parties (i.e. Product Owner and Engineering). By developing a meaning-rich vocabulary (and implementing Features in these terms is what facilitates this shared semantic), you can bring the two sides of the team closer together. It means that misunderstandings can bubble-up faster (remember, the sooner you discover a defect, the cheaper it is to fix it, generally).<br />
</div><div class="paragraph">For the users of the program, the UI <em>is</em> the software. Keep Feature Steps in terms of the UI.<br />
</div><h4>Stay DRY</h4><div class="paragraph">DRY is an acronym for "Do not Repeat Yourself" (see <a href="http://en.wikipedia.org/wiki/Don't_repeat_yourself">Wikipedia article</a> for a deeper explanation) and it applies just as well to Feature Step writing as it does to software coding. The essence of this advice is to take care to minimize the amount of overlap between Features and their Scenarios. This is another example of where it pays significantly for the Business Analyst to have a working understanding of how the software is functioning. If you have already written a Feature+Scenario that exercises selecting an item from a list, no need to repeat verifying that behavior in another Feature+Scenario.<br />
</div><h5>Features as Functional Tests</h5><div class="paragraph">This become especially poignant as the project grows in size. One of the enemies of Continuous Integration (CI) is long running builds.<br />
</div><div class="paragraph">Long running CI builds create all kinds of problems: they lengthen the commit dance which slows down disciplined developers; they dampen the whole value of the <em>Continuous</em> nature of CI because the time window is larger and thus more developers' changes start to pile on. Builds that push past 15 minutes start to manifest these issues. So, in the interest of maintaining the value of Continuous Integration, the full build (which includes unit and functional tests) should run as fast as possible.<br />
</div><div class="paragraph">Quite often the longest running component of these full builds are the functional tests. Cucumber Features in the context of requirements analysis are User Stories; but Cucumber Features in the context of verification (i.e. executing the Scenarios) are, in fact, functional tests. To the extent that there is coverage overlap of Scenarios, we're duplicating computing effort (exercising the software in nearly the same way) with no added value. This waste needlessly extends the length of the overall build. In this way, Features written in an unskillful way, endanger the value of the CI-build -- a cornerstone of Agile software development.<br />
</div><div class="paragraph">The Business Analyst, as the author of Features and their Scenarios, is a key player in ensuring that the functional test portion of the overall build executes as quickly as possible without reducing the confidence in correctness that these tests bring.<br />
</div><h3>Practices for the Writing Cucumber Features</h3><div class="paragraph">With those two guidelines in mind:<br />
<ul><li>Write Features with the Business Analyst Mindset;</li>
<li>Keep Features DRY.</li>
</ul>Here are some concrete techniques that I have found to work in the field.<br />
</div><h4>Create and maintain a lexicon of Cucumber Steps</h4><div class="paragraph">Create a page in the project wiki (in the Requirements section) to maintain a dictionary of Cucumber Steps. Organize the page into sections including:<br />
<ul><li>a section for common steps;</li>
<li>a section for each major feature set;</li>
<li>a glossary of Domain and Visible Software Terms.</li>
</ul>Within the Step definition sections, include a table with the following columns: <ul><li><b>Template</b> -- the generic form of the Step, with clear typographic notation to indicate the "variables" in the Step.</li>
<li><b>Meaning</b> -- description of what the Step does. Include a clear definition for each "variable" and the expected set of values.</li>
<li><b>Examples</b> -- like all technical documentation, nothing makes it clearer than a reasonable example. Unless it is absolutely obvious how one would write a step, include at least one example.</li>
</ul></div><div class="paragraph">In essence, this page codifies of the shared vocabulary of the project's requirements. The Product Owner (through the Business Analyst) contributes Domain-specific terms and their bias to the solution. The Engineering team (again, through the Business Analyst) contributes names of widgets and other UI terms. </div><div class="paragraph">This page acts as a palette while writing new Feature+Scenarios. The goal should be to identify reusable steps (and often refactor an exist set of steps into a more generic one, especially in the early days of the project) and write new Feature Step using the existing vocabulary as much as possible. Don't torture the language, just look for reuse. At the same time, new Features bring new UI interactions and it is through writing Features that new kinds of Steps are discovered. As they are, add them to the page.</div><div class="paragraph">When we look at this documentation from an Engineering perspective, as we delve into how to <em>implement</em> Cucumber Feature Steps), we will see how technical concerns have an equal influence over the shape of this lexicon. In the end, however, this document sits squarely in the Business Analyst's area of concern. </div><br />
<h4>Marry an Issue Tracking Ticket with a Cucumber Feature to Form Each User Story</h4><div class="paragraph">A User Story, in its best incarnation, is a token to remind the team of an understanding around a specific requirement. In practice, there are two parts to a User Story, then: <ol><li>the lastest set of agreements, written down to the extent that the team need to so as to not forget.</li>
<li>the workflow of the User Story, from conception to sign-off, tracked to help keep the whole team on the same page as to the "status" of this feature.</li>
</ol>One way to handle these two aspects of the User Story is to create a Ticket in a flexible issue tracking system (I have personally used JIRA in the past and <em>loved</em> it...I'll share more about how Atlassian's JIRA+Confluence combo is a killer solution to most software development efforts) and to link that Ticket to the <code>.feature</code> file checked into the VCS. </div><div class="paragraph">The role of the JIRA ticket is to track the workflow and discussion amongst the team regarding the user story. The <code>.feature</code> is the home for the rest of the User Story: the narrative and agreed-upon details. </div><div class="paragraph">"Linking", here is simply including the URL to each artifact in the other: <ul><li>in the JIRA ticket, include the URL to the <code>.feature</code> file in your VCS via its web interface (both SVN and git provide these, OOTB, others may as well);<br />
<li>in the <code>.feature</code> file, include the URL to the JIRA ticket.</li><br />
</ul></div><h4>Start with Two Scenarios: Main Flow and One Exception Flow</h4><div class="paragraph">The practical starting point for keeping Features DRY is to start with the minimal set of Scenarios for each feature. Begin with just two Scenarios: one that covers the Main Flow or "Happy Path" and one that covers what happens when there's an "exceptional case" (usually responding to invalid user input). </div><div class="paragraph">The key idea is to include an additional scenario only when an additional UI treatment is introduced. In the Happy Path, there is often a message that indicates "success" to the user (e.g. "Your journal entry has been recorded."). However, if the user input fails form validation, for example, then the elements that make clear to the user how to fix the input now come into play (including highlighting around text fields and the presence of the error message in the status area. </div><div class="paragraph">Once those mechanisms are exercised, the value of testing for all the other conditions that cause these UI mechanisms to activate is significantly reduced. Such tests are better suited as verifications against the underlying "model" code: unit tests (i.e. verifying that when the application attempts to use invalid input, the validation code catches it). Here we see, again, that the Business Analyst, the author of these functional tests, is well-served understanding how the underlying software is structured. </div><h3>Next...</h3><div class="paragraph">In this installment of our series, we've covered what life is like for the Business Analyst writing Cucumber Features. Hopefully, this description starts to illuminate the power of using Cucumber Features as the center-piece for BDD -- since they clearly sit right in the Business Analyst's purview, they are the confluence of business desire and software implementation concerns. Because Features are executed (and indeed, incorporated in the CI build), the Business Analyst looks to keep the formal Scenarios tight and DRY. </div><div class="paragraph">With the bryne starting to work its way in, we're ready to turn our attention to the full pickling process: how the raw requirements meet and become infused with the salty world that is software engineering. We shift our focus from the Business Analyst to the Engineering team who is writing the Ruby code to implement the Cucumber Steps... </div>JTiggerhttp://www.blogger.com/profile/09997166268958276999noreply@blogger.com1tag:blogger.com,1999:blog-6564406823087294113.post-7273799748748645932011-01-10T07:23:00.000-08:002011-01-31T10:11:35.718-08:00Relishing in Perfection Part 2 -- Ingredients<style>
.paragraph {
padding-bottom: 1em;
}
.quote {
margin-left: 1em;
padding: 1em; 0em; 1em; 0em;
}
.keypoint {
color: #AD9;
font-style: bold;
}
h4 {
text-transform: uppercase;
}
</style><br />
<div class="paragraph">Installment two of "Relishing Perfection", a series on doing BDD with Cucumber, "the right way". If you have not already, read <a href="http://software-as-a-craft.blogspot.com/2011/01/relishing-in-perfection-doing-bdd-right.html">the introduction</a>.<br />
</div><h1>Reflections</h1><div class="paragraph">And now, some observations from having experienced BDD and Cucumber, up-close...<br />
</div><h3>Feature Authoring is Iterative</h3><div class="paragraph">Like everything else in Agile development, arriving at the "right" way to express acceptance criteria is best done organically, iteratively. There are certainly patterns to be hatched, but this is a developed capability. Expect to learn as you go and to be evolving your own <a href="http://www.martinfowler.com/bliki/BusinessReadableDSL.html">Business Readable DSL</a>.<br />
</div><div class="paragraph">I share a number of "low-hanging fruit" Cucumber Step "templates", in a future post.</div><h3>Feature Authoring Facilitates the Requirements Conversation</h3><div class="paragraph">Features are executable requirements. In fact, there are essentially User Stories. They are discovered, evolved and revised as such. Because Features live in the gap between the Customer and the Developer, both perspectives are required: what the Customer wants and what the Developer can deliver.</div><h3>Features are Managed by the Business Analyst</h3><div class="paragraph">We're engaged in zeroing-in on the right software solution to the business problem at hand. The traditional role that owns that quest is the Business Analyst. In a mature Behavior-Driven Development project, the Business Analyst is the perfect role to be the "Chef-in-Chief". The Business Analyst:<br />
<ul><li>develops a solid understanding of the business domain (the problem domain); so much so that she can regularly speak for the customer in their stead.</li>
<li>also learns well what's possible in software (the solution domain); again to the point where she can often represent for the development team in their absence.</li>
<li>seeks out, captures and develops requirements; like a sleuth, hunts down missing details; brokers the best feasible solution.</li>
<li>ensures that the software developed meets not just the letter of the requirements but the spirit of the envisioned solution.</li>
</ul>Now, in your organization, you may or may not have a title "Business Analyst" but these concerns must be managed by one or more project team members. Sometimes, the Project Manager doubles as the Business Analyst (in all but the smallest or least inventive efforts, a real mistake). Sometimes the Technical Lead also picks-up some BA responsibilities (again, this role is usually a full-time effort in itself). It's not important that there be a person with this title. It's important to have this mindset and perspective active when writing Features.</div><div class="paragraph">If you approach your Features as if you are the Business Analyst, you'll:<br />
<ul><li>more naturally use language that is understood by both business folks and developers; Martin Fowler describes this as <a href="http://www.martinfowler.com/bliki/BusinessReadableDSL.html">Business Readable DSL</a>.</li>
<li>know how important it is to use language consistently and do so when writing stories and features;</li>
<li>know when the Scenarios of a Feature are "good enough" to begin development.</li>
</ul></div><h3>All the World's a Nail...</h3><div class="paragraph">Like with any new tool, it's easy to over-apply its use. In the case with Cucumber, especially in the context of the BDD process, it is easy to get caught up in writing a ton of Scenarios. This is requirements driving development, isn't it?!<br />
</div><div class="paragraph">It's true: we are developing software to serve the business, not the other way around. However, the pragmatic reality is that even with a simulated browser (OOTB, Cucumber uses Capybara) these tests take time to run... far more time than targeted unit tests/specs. Here, the principle of being test-driven meets the principle of "Don't Repeat Yourself". The balance is in <em>enough</em> functional tests (which is really what Features are: executable Functional Specs) to give confidence that the UI is generally working, backed by unit tests/specs that ensure the underlying components operate in the larger set of expected conditions.<br />
</div><div class="paragraph">When we started our project, we were purely driven by Scenario development... which is not a bad place to <b>start</b>. Establish those habits early. It wasn't long (the second or third feature) that our CTO (with a fresh perspective on the project) observed that we were writing a lot of Scenarios... one for each validation case. When we looked closer, we realized that we were repeating a lot of setup and clicking around <em>just</em> to verify that the right error message was displayed...<br />
</div><h3>Be Agile: Inspect and Adapt</h3><div class="paragraph">So, we adjusted. We realized that there was little incremental value being produced with these additional Scenarios over the "Happy Path" and one "Invalid Input" pair. The important additional cases (e.g. the whole rest of the possible flavors of invalid inputs) could easily be tested (and cheaply, both in coding and execution time) in the unit test environment (in this case, RSpec).<br />
</div><div class="paragraph">The key is that we made sure we were not slaves to the process. This is a core value in <a href="http://agilemanifesto.org/">the Agile approach</a>: give more credence to educated and contextualized perspective than process dogma. This is <em>part</em> of what it means to value individuals and interactions over process.<br />
</div><h3>Next...</h3><div class="paragraph">With that, we are ready to start pickling. There are two major skill sets to develop: authoring Features themselves and developing the code for the Cucumber Steps. They feed into each other but have their own concerns as well. In the next session, I will dive in the first of these two skills, <a href="http://software-as-a-craft.blogspot.com/2011/01/relishing-in-perfection-iii-writing.html">hatching principles and practices the Business Analyst should use when authoring Features</a> in the first place.<br />
</div>JTiggerhttp://www.blogger.com/profile/09997166268958276999noreply@blogger.com0tag:blogger.com,1999:blog-6564406823087294113.post-56436941658962553302011-01-08T17:18:00.000-08:002011-02-01T08:13:50.657-08:00Relishing in Perfection: Doing BDD the Right Way with Cucumber<style>
.paragraph {
padding-bottom: 1em;
}
.quote {
margin-left: 1em;
padding: 1em; 0em; 1em; 0em;
}
.keypoint {
color: #AD9;
font-style: bold;
}
h4 {
text-transform: uppercase;
}
</style><br />
<div class="paragraph">This last week, I ramped-off the first project I've been on using Behavior-Driven Development. It was a Rails project and so we used Cucumber. As I looked out across the web, I found a few beacons around the actual practice of BDD. There's some good stuff out there, but I couldn't find anyone that is going past the theory and sharing the tacit knowledge. This is my first attempt at starting to fill that gap.<br />
</div><div class="paragraph">First, I want to contextualize BDD and share some resources to get started. Then, I give some concrete observations from our project and lay out a set of principles and practices that we followed or discovered.<br />
</div><h1>Advancing The State of The Craft</h1><div class="paragraph">Dan North's <a href="http://en.wikipedia.org/wiki/Behavior_Driven_Development">Behavior-Driven Development</a> (BDD) is proving to be another key stepping stone in our journey, as an industry, toward what <a href="http://en.wikipedia.org/wiki/Robert_Cecil_Martin">Uncle Bob</a> has called "professional craftsmanship".</div><div class="paragraph">One of the fundamental practices in BDD is "<a href="http://en.wikipedia.org/wiki/Outside-in_software_development">outside-in programming</a>". Programming efforts are driven by attempting to meet a specification of the software's behavior as quickly as feasible. "Specification" sounds pretty rigorous, doesn't it? ...and a good specification is.</div><div class="paragraph">The most useful specifications come with examples that concretely express what "done" looks like. This is really important, because if you can get <em>this</em> concrete, you can start to have meaningful conversations between the folks driving the business and the kids writing the software. Paired with visuals (story board of wireframes ... aka "User Interaction Designs"), you can really start to have design discussions with actionable, trackable results.<br />
</div><div class="paragraph">In this way, examples facilitate quality communication -- the means by which intention, vision, and detail are shared. Good stuff! But wait, there's more...</div><div class="paragraph">What if you could express your specifications as examples in a concrete enough way that you could automate the execution of them? Suddenly, the "definition of done" is more firm (if the automated example doesn't run successfully, you're not done). And if the examples are abstract enough, we can expect to execute them not just when the software is developed, but also as part of a regression suite. Each time you run it, reinforcing your confidence that "everything is still working." Wow! That's a lot of value being realized. But can it be done?</div><div class="paragraph">We're really asking...<br />
<div class="quote">Is it possible to express acceptance criteria as examples that they:<br />
<ul><li>are written in language <span class="keypoint">understood by both business and software people</span>?</li>
<li>are meaningful enough to act as <span class="keypoint">a catalyst for converging on the "right" software solution to the business problem</span> at hand?</li>
<li>are concrete enough to be <span class="keypoint">executed by a computer</span>?</li>
<li>are abstract enough that one can expect them to <span class="keypoint">function long after that part of the software is complete</span>?</li>
</ul></div>That's a tall order... but that's exactly what BDD is driving towards. If we have any hope of getting there, we'd have to see:<br />
<ol><li>exactly how these kinds of examples are written;</li>
<li>precisely how they are made executable with a minimal effort;</li>
<li>and how these practices fit within the software development process.</li>
</ol></div><div class="paragraph">Thankfully a <em>huge</em> chunk of this ground has been expertly covered. David Chelimsky covers the very foundations of this discussion in <a href="http://www.pragprog.com/titles/achbd/the-rspec-book"> The RSpec Book: Behaviour-Driven Development with RSpec, Cucumber, and Friends</a> (with significant contributions from Dave Astels (author, RSpec contributor and BDD evangelist), Zach Dennis, Aslak Hellesøy (original author of Cucumber), Bryan Helmkamp, and Dan North (credited with the term BDD and write RSpec's “Story runner," the predecesor to Cucumber). As Robert Martin eloquently explains in the forward (posted right on the PragProg page for the book), the title of the book is misleading; it would be more aptly titled "BDD in Action".</div><div class="paragraph">Before he published his book, Chelimsky developed a presentation:<br />
<a href="http://chariotsolutions.com/slides/pdfs/ete2008-IntegrationTestingWithRSpec.pdf">Integration Testing in Ruby with RSpec’s Story Automation Framework</a>" which contains enough detail that it <em>can</em> be read on its own.</div><div class="paragraph">What Chelimsky's book covers is how Behavior-Driven Design can be realized in the Ruby world (and specifically when developing web applications with Ruby on Rails). As the title suggests, his book walks you through a mini website development project employing the BDD approach to identify and using <a href="https://github.com/aslakhellesoy/cucumber/wiki">Cucumber</a> to write the very kinds of executable Examples we're talking about here.</div><div class="paragraph">While not the first of its kind, Cucumber has in the past year become the tool of choice for Rails shops that are adopting BDD <br />
(Alex Handy wrote a fine article about Cucumber and included its brief history, titled "<a href="http://www.sdtimes.com/blog/post/2010/09/13/Peeling-Cucumber.aspx">Peeling Cucumber</a>").</div><div class="paragraph">What's needed, then, are reports from the field: empirical data and observations that fill-in the important details on how a project team realizes the benefits of BDD.</div><div class="paragraph">Having completed my first such effort, I'm no expert. But I have done it and definitely have some insight to share. In the next weeks, I will be unspooling a series that makes an attempt to being to fill this gap:<br />
<ul><li>sharing observations about how Feature authoring unfolds, in practice;</li>
<li>enumerating principles and practices around writing Features and Scenarios;</li>
<li>discuss similar guidelines for smartly implementing your Cucumber Steps;</li>
<li>suggesting a set of common templates and features that you should include in your Example automation suite.</li>
</ul>If you're getting started with BDD and Cucumber, yourself, I hope you'll join me. Better yet, I hope you'll engage me in the conversation and share YOUR experiences: we're much smarter together than running solo. Let's relish!<br />
</div><h3>Next...</h3><div class="paragraph">Before we charge into the kitchen and start mixing it up, let's take a quick survey of the <a href="http://software-as-a-craft.blogspot.com/2011/01/relishing-in-perfection-ii-chef-in.html">Ingredients</a>...<br />
</div><h1>References:</h1><h3>Professional Craftsmen</h3><ul><li><a href="http://en.wikipedia.org/wiki/Robert_Cecil_Martin">http://en.wikipedia.org/wiki/Robert_Cecil_Martin</a></li>
<li><a href="http://www.infoq.com/presentations/craftmanship-ethics">http://www.infoq.com/presentations/craftmanship-ethics</a></li>
<li><a href="http://en.wikipedia.org/wiki/Dave_Astels">http://en.wikipedia.org/wiki/Dave_Astels</a></li>
<li><a href="http://www.linkedin.com/pub/zach-dennis/1/aa1/38">http://www.linkedin.com/pub/zach-dennis/1/aa1/38</a></li>
</ul><h3>Behavior-Driven Development</h3><ul><li><a href="http://behaviour-driven.org/">http://behaviour-driven.org/</a></li>
<li><a href="http://en.wikipedia.org/wiki/Behavior_Driven_Development">http://en.wikipedia.org/wiki/Behavior_Driven_Development</a></li>
<li><a href="http://www.pragprog.com/titles/achbd/the-rspec-book">http://www.pragprog.com/titles/achbd/the-rspec-book</a></li>
<li><a href="http://www.martinfowler.com/bliki/BusinessReadableDSL.html">http://www.martinfowler.com/bliki/BusinessReadableDSL.html</a></li>
</ul><h3>Cucumber</h3><ul><li><a href="http://cukes.info/">http://cukes.info/</a></li>
<li><a href="http://www.slideshare.net/bkeepers/behavior-driven-development-with-cucumber-presentation">http://www.slideshare.net/bkeepers/behavior-driven-development-with-cucumber-presentation</a></li>
<li><a href="http://elabs.se/blog/15-you-re-cuking-it-wrong">http://elabs.se/blog/15-you-re-cuking-it-wrong</a></li>
<li><a href="http://mislav.uniqpath.com/2010/09/cuking-it-right/">http://mislav.uniqpath.com/2010/09/cuking-it-right/</a></li>
</ul>JTiggerhttp://www.blogger.com/profile/09997166268958276999noreply@blogger.com0tag:blogger.com,1999:blog-6564406823087294113.post-47889038862965797592010-12-29T11:32:00.000-08:002010-12-29T11:52:16.928-08:00Empowering DelegationMy buddy, Tom Looy, shared this article with me:<br />
<blockquote><a href="http://www.babusinesslife.com/Tools/Features/The-art-of-action-.html">http://www.babusinesslife.com/Tools/Features/The-art-of-action-.html</a></blockquote>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.<br />
<br />
Bungay notes:<br />
<ul><li>you cannot anticipate the unforeseeable; to attempt this false precision can result in demoralization, original guidance becoming misguidance;</li>
<li>as delegation occurs, each level should add only the amount of detail they can actually provide, no more;</li>
<li>prefer delegation in person;</li>
<li>at each delegation, the superior verifies by requesting an action plan from the subordinate.</li>
<li>delegation comes in the form of Auftrag: a task and a purpose; Bungay refers to this as "direct opportunism"</li>
</ul><div><span class="Apple-style-span" style="font-size: large;"><b>Mission Command</b></span></div><div><br />
</div><div>The essences of this approach:<br />
<br />
<ul><li>vision is shared from top-to-bottom; alignment of strategy</li>
<li>action is contextualized; tactics are appropriate for the situation</li>
<li>all levels enjoy an appropriate autonomy (and therefore have a chance to enjoy a sense of ownership)</li>
</ul><br />
He suggests the following practices:<br />
<br />
<ul><li><b>decide what really matters</b> -- use what knowledge is available to you (experience + known circumstances); don't extend plans beyond what you can clearly see.</li>
<li><b>get the message across</b> -- hand down goals and rationale; then request a plan.</li>
<li><b>give people space and support</b> -- set boundaries that prevent disaster and allow all else.</li>
</ul><div><span class="Apple-style-span" style="font-size: large;"><b>Observations</b></span></div></div><div><ul><li>This is a fine way of expressing the tension between autonomy and alignment.</li>
<li>This approach assumes that you can get alignment of strategy. That you can achieve a shared vision.</li>
<ul><li>Do this in part by not just handing down strategy, but contextualize as much as is feasible.</li>
</ul><li>You have to really figure out how to express the right boundaries:</li>
<ul><li>can a developer introduce a new architecture component?</li>
<li>can an architect decide how scalable the site should be?</li>
</ul><li>This approach gets complicated when the subordinate is less experienced (usually the case?); this is how the organization develops and learns.</li>
<ul><li>this is the nuts and bolts of growth: can you mentor/coach without taking over?</li>
<li>perhaps this is the practical way of identifying boundaries; initially leave it open to anything, have regular reviews and adjust.</li>
<ul><li>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)</li>
</ul></ul></ul></div><div><i>(todo: include an example)</i></div>JTiggerhttp://www.blogger.com/profile/09997166268958276999noreply@blogger.com0tag:blogger.com,1999:blog-6564406823087294113.post-31786472833335343562010-12-14T15:59:00.000-08:002011-01-10T07:29:54.223-08:00How 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.<br />
<br />
<b><span class="Apple-style-span" style="font-size: large;">The story so far...</span></b><br />
I had been working my way through the excellent text <a href="http://pragprog.com/titles/rails4/agile-web-development-with-rails">Agile Web Development with Rails (4th edition)</a> by Sam Ruby.<br />
<br />
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:<br />
<br />
<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">$ rake test</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">.</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">.</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">.</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"><br />
</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">Started</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">.F....................</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">Finished in 0.538259 seconds.</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"><br />
</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> 1) Failure:</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">test_should_destroy_cart(CartsControllerTest) [test/functional/carts_controller_test.rb:43]:</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">"Cart.count" didn't change by -1.</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"><1> expected but was</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"><2>.</span><br />
<div><br />
</div><div>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. :-/ </div><div><br />
</div><div><span class="Apple-style-span" style="font-size: large;"><b>git bisect to the rescue!</b></span></div><div>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.</div><div><br />
</div><div>With git bisect, I was able to quickly hunt down the offending commit.</div><div><br />
</div><div>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":</div><div><br />
</div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">(master) $ git branch -f last_known_good 4586dadff6cf838262c48149ca265e5226cb80b1</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">(master) $ git co last_known_good</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">(master) $ rake test</span></div><div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">Started</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">...............</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">Finished in 0.399094 seconds.</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"><br />
</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">15 tests, 24 assertions, 0 failures, 0 errors, 0 skips</span></div></div><div><br />
</div><div>I then initialized a bisect session:</div><div><br />
</div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">(master) $ git bisect start</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">(master) $ git bisect bad</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">(master) $ git bisect good last_known_good</span></div><div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">Bisecting: 3 revisions left to test after this (roughly 2 steps)</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">[8aab0db8f45e8a0c2f5ffc10e6f2382d90c249e8] (E1,E2) Added 'quantity' to Line Items</span></div></div><div><br />
</div><div>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 <i>actual</i> last known good commit (or even better, the <i>first</i> known bad commit) is identified.</div><div><br />
</div><div>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 <i>actual</i> last known good:</div><div><br />
</div><div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">jonamac:work ((no branch))$ rake test</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">Started</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">......................</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">Finished in 0.541449 seconds.</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"><br />
</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">22 tests, 34 assertions, 0 failures, 0 errors, 0 skips</span></div></div><div><br />
</div><div>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)</div><div><br />
</div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">$ git bisect good</span></div><div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">Bisecting: 1 revision left to test after this (roughly 1 step)</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">[a17b740253ceb4e4df5f055808aa638bee269a7f] (F1) Added partials for Line Item and Cart.</span></div></div><div><br />
</div><div>git tells me that he's moved to that midpoint. I just run my rake test again and tell git about the results.</div><div><br />
</div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">$ rake test</span></div><div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">Started</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">.F.E...F.EEEE...EEEE..</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">Finished in 0.557046 seconds.</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"><br />
</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> 1) Failure:</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">test_should_destroy_cart(CartsControllerTest) [test/functional/carts_controller_test.rb:43]:</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">"Cart.count" didn't change by -1.</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"><1> expected but was</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"><2>.</span></div></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">...</span></div><div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">22 tests, 22 assertions, 2 failures, 9 errors, 0 skips</span></div></div><div><br />
</div><div><br />
</div><div>oiy! this one is bad! Again, I just tell git that fact and it continues to hunt down the source...</div><div><br />
</div><div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">$ git bisect bad</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">Bisecting: 0 revisions left to test after this (roughly 0 steps)</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">[bc5470e9380a80448fd61e780d24fe8e1927dec3] (E3) Improved cart display.</span></div></div><div><br />
</div><div>...run my test again...</div><div><br />
</div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">$ rake test</span></div><div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">Started</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">.F....................</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">Finished in 0.538259 seconds.</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"><br />
</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> 1) Failure:</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">test_should_destroy_cart(CartsControllerTest) [test/functional/carts_controller_test.rb:43]:</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">"Cart.count" didn't change by -1.</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"><1> expected but was</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"><2>.</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"><br />
</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">22 tests, 33 assertions, 1 failures, 0 errors, 0 skips</span></div></div><div><br />
</div><div>and as I tell git about this last one (that I see the failure), it reports that it has found the first bad commit:</div><div><br />
</div><div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">$ git bisect bad</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">bc5470e9380a80448fd61e780d24fe8e1927dec3 is the first bad commit</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">commit bc5470e9380a80448fd61e780d24fe8e1927dec3</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">Author: John S. Ryan <john@tacitknowlege.com></john@tacitknowlege.com></span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">Date: Sun Dec 12 18:00:54 2010 -0800</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"><br />
</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> (E3) Improved cart display.</span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> </span></div><div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> + Added ability to empty cart.</span></div><div><br />
</div></div><div>There we go! Something in this commit is the root cause of my issue. gitk can be helpful to visualize what happened:</div><div><br />
</div><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/_Fm7PE4JQA9E/TQfMrRRZSVI/AAAAAAAAAnA/7KCFVKtS4kI/s1600/gitk-of-git-bisect-session.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/_Fm7PE4JQA9E/TQfMrRRZSVI/AAAAAAAAAnA/7KCFVKtS4kI/s1600/gitk-of-git-bisect-session.png" /></a></div><div><br />
</div><div>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.</div><div><br />
</div><div>Now I've got my smoking gun.</div><div><br />
</div><div>What's left is to simply check out at the <i>actual</i> 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").</div><div><br />
</div><div><span class="Apple-style-span" style="font-size: large;"><b>Post Mortem</b></span></div><div>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.</div><div><br />
</div><div>That said, it's nice to have a safety net. Lucky for me, I <i>was</i> 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.</div><div><br />
</div><div><br />
</div>JTiggerhttp://www.blogger.com/profile/09997166268958276999noreply@blogger.com0tag:blogger.com,1999:blog-6564406823087294113.post-60410489339043445102010-10-17T08:21:00.000-07:002011-01-10T07:30:53.542-08:00Running Things on Multiple MacsI use <a href="http://culturedcode.com/things/">Things</a> as my core Trusted System in my personal <a href="http://en.wikipedia.org/wiki/Getting_Things_Done">GTD</a>. I wanted to use it on both my work computer and my personal machine. At the time of writing, Culture Code is <i>planning</i> on providing Mac-to-Mac sync. In the meantime, there's a reasonable workaround.<br />
<br />
The basic idea is to put your Things database on a "Disk in the Cloud". You can use <a href="http://amplicate.com/services/4275-top-ten-online-data-storage/">any such service</a>. While I am using iDisk (comes with MobileMe), I'm current transitioning off that onto <a href="https://www.dropbox.com/home">Dropbox</a>. I found incomplete instructions online (including <a href="http://culturedcode.com/things/wiki/index.php/Syncing_Things_on_multiple_Macs_%28FAQ%29">Culture Code's FAQ</a>). Here's a step-by-step walkthrough.<br />
<br />
<b><span class="Apple-style-span" style="color: red;">WARNING</span></b>: According to <a href="http://culturedcode.com/things/wiki/index.php/Syncing_Things_on_multiple_Macs_%28FAQ%29">Culture Code's FAQ on this matter</a>, you should NOT share a Things database where one machine is running Tiger (10.4) and another is running Leopard (10.5) or better.<br />
<br />
<b><span style="color: red;">WARNING</span></b>: Make sure you take a backup of your Things database (at the very least, run a Time Machine backup) before you begin.<br />
<br />
<b><span style="color: red;">WARNING</span></b>: 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.<br />
<br />
<br />
<h2>Step 1: Install Things on Both Computers</h2><div>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.</div><div><br />
</div><div><h3>On your second computer:</h3></div><div><ol><li>Download Things from Cultured Code: <a href="http://culturedcode.com/things/">http://culturedcode.com/things/</a><br />
<i><br />
It downloads as a ZIP file which your Mac will automatically unzip; the file that ultimately lands is Things.app.<br />
<br />
<br />
</i></li>
<li>Copy Things.app to your /Applications folder.<br />
<br />
<i>Just like you would to install any Mac application.</i><br />
<br />
</li>
<li>Run Things.app to initialize it.<br />
<br />
<i>You're allowing this second installation of Things to create all the folders and empty database.</i><br />
<br />
</li>
<li>Add your License to this installation!<br />
<br />
<i>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.</i><br />
<br />
</li>
<li>Quit Things<br />
<br />
<i> You're about to move the database for Things; best to quit the application before you do.</i></li>
</ol></div><div><br />
</div><div><h2>Step 2: Move the Things Database to a Shared Drive</h2></div><div>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).<br />
<br />
<h3>On your first computer:</h3><ol><li>Make sure Things is NOT running.<br />
<br />
<br />
</li>
<li>Create a folder <b>on your shared drive</b> to be the new home for your shared Things database.<br />
<br />
<i>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.<br />
<br />
For iDisk users, the base directory is </i><br />
<br />
<div style="padding-left: 2em;"><code>/Volumes/iDisk</code></div><br />
<i>and for Dropbox users, by default your Dropbox folder is located at:</i><br />
<br />
<div style="padding-left: 2em;"><code>~/Dropbox</code></div><br />
<i>creating the directory that will be the home of your Things database:</i><br />
<div><code><br />
$ mkdir "~/Dropbox/Library/Application Support/Cultured Code/" <span style="color: grey;"><i>(don't forget the quotes)</i></span><br />
</code></div><br />
</li>
<li>Now, move your Things Database to this new location:<br />
<br />
<div><code><br />
$ mv "~/Library/Application Support/Cultured Code/Things" \<br />
"~/Dropbox/Library/Application Support/Cultured Code/"<br />
<span style="color: grey;"><i>(quotes are required!)</i></span><br />
</code></div><br />
</li>
<li>Now, we'll create a soft link from to where Things <i>thinks</i> the database to where you've just moved it:<br />
<div><code><br />
$ cd "~/Library/Application Support/Cultured Code/"<br />
$ ln -s "~/Dropbox/Library/Application Support/Cultured Code/Things" Things<br />
</code></div><br />
</li>
<li>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).<br />
<div><code><br />
$ open -a Things<br />
</code></div><br />
<i>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).</i><br />
<br />
</li>
<li>Quit Things.</li>
</ol></div><br />
<h3>On your Second Computer</h3><br />
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.<br />
<br />
<ol><li>Make sure Things is NOT running.<br />
<br />
<i>If you're running Things while you modify the location of the database, you risk corrupting the database.</i><br />
<br />
</li>
<li>Delete the empty Things database.<br />
<br />
<div><code><br />
$ rm -r "~/Library/Application Support/Cultured Code/Things"<br />
</code></div><br />
</li>
<li>Link the Things database from the shared drive:<br />
<br />
<div><code><br />
$ cd "~/Library/Application Support/Cultured Code/"<br />
$ ln -s "~/Dropbox/Library/Application Support/Cultured Code/Things" Things<br />
</code></div><br />
<i>Now, you have both computers soft-linking to the shared folder.</i><br />
<br />
</li>
<li>Fire-up Things, here, to ensure that you're now able to read the shared database on <b>this</b> computer as well.<br />
<br />
<i>If you see all your data, you've done it. There's one more step, but grats, the sharing has started.</i><br />
</li>
<li>Quit Things.<br />
</li>
</ol><br />
<h2>Write a Script to Launch Things from a Lock File</h2><br />
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.<br />
<br />
<h3>On the First Computer</h3><br />
<ol><li>Create the script "Things.sh" on the Shared Drive.<br />
<br />
<i>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).</i><br />
<div><code><br />
$ cd "~/Dropbox/Library/Application Support/Cultured Code/"<br />
$ touch Things.sh<br />
$ open -a TextEdit Things.sh<br />
</code></div><br />
<i>In this script include the following (I recommend you copy and paste instead of keying this all in yourself):</i><br />
<br />
<div style="border: 1px solid; padding: 5px;"><pre>#/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
</pre></div></li>
<li>Save the file.</li>
<li>Set the script to be "executable" on both computers.<br />
<br />
<i>On <b>BOTH</b> computers run this command:</i><br />
<br />
<div><code><br />
$ chmod +x "~/Dropbox/Library/Application Support/Cultured Code/Things.sh"<br />
</code><br />
</div><br />
</li>
<li>Launch Things from the shell script.<br />
<br />
<i>Now, every time you want to start-up Things, fire-up this script.</i><br />
</li>
</ol>JTiggerhttp://www.blogger.com/profile/09997166268958276999noreply@blogger.com0