Why we focus on solutions, not products

September 21st, 2006 | 2 comments

Developing anything in the digital realm can be fantastically rewarding precisely because the ropes on our wings of fantasy are greatly weakened. We can imagine practically anything, and we can implement a lot of what we can imagine. The whir of excitement in our stomachs at the vision of some excellent idea prompts my favorite utterance, “Wouldn’t it be cool if…”

Unfortunately, our flights of fantasy must interface with the “physical” world, even if through the digital medium. Faster than light travel doesn’t exist. Perpetual motion doesn’t exist. There is a cost for everything, and it’s thousands of orders of magnitude greater than the few molecules of ATP that literally fueled our fantasy.

No great revelations there. But it sure helps to ground ourselves with this frequent reminder.

When it comes to software, I find that the single most difficult thing for anyone to do is think outside of the Feature Fantasy. This applies to everyone, developers, designers, clients, users, people. Everyone. With a breathless hop, skip, and jump, we leap right over the problem, the solution, and into some particular implementation. It’s so automatic I would have assumed that we’d been taught how to do this in specialized classes since grade school, with some folks getting advanced university degrees in it.

At PLANET ARGON, one thing we are trying to develop through our process and our exploration of d3, is how to effectively separate the problem, the solution, and the implementation. I would say better than 90% of our discussions start out at the feature, or implementation level. We actively and tireless attempt to refocus this back on the problem, describing and documenting it so we can think and dialogue about solutions.

As Jeremy has explored a bit in his article on Feature Ecology, the interaction or relation between features actually alters the product as a whole. Again, not an astounding idea, but the consequence of this is that a lot of time is spent chasing your tail if you focus on features without the benefit of well understanding the problem and solution.

Another benefit of focusing on the problem and solution is that it provides a natural meeting ground between clients and developers. Often the clients are not the users of the software. This makes it even more important to explore the problem to be solved rather than the client’s ideas of particular features. One of the most valuable things I took from reading Alan Cooper’s original About Face almost a decade ago is that no one would use your software if they didn’t have to. How’s that for a response to, “wouldn’t it be cool if…”

If you want to create software that people won’t hate using, focus on the problem and a very good description of a solution. Use your solution to implement a product. Get feedback from people on your understanding of the problem, your description of a solution, and only lastly, on your implementation of the product.

Are your tests findable

September 20th, 2006 | 2 comments

I probably use Google search on average 20-30 times a day. It’s an indispensable tool for me. It’s definitely not the last word in search engines. I can imagine a lot of additional functionality, like contextualizing my searches over time or within categories. But overall, it works reasonably well.

Lately, I’ve been reading Ambient Findability by Peter Morville. He provides this definition for findability:

findability n
  1. The quality of being locatable or navigable.
  2. The degree to which a particular object is easy to discover or locate.
  3. The degree to which a system or environment supports navigation and retrieval.

Peter explains that findability is defined at both the object and system levels. For example, the title of a document or the color of a pen on your desk. At the system level, you can ask questions like, “Can a traveler navigate an airport,” or “Can a user navigate a website.”

While these ideas help us frame the concepts, how do we evaluate findability in concrete situations. Not surprisingly, there are elements of findability that we can examine:

Findability requires definition, distinction, difference. In physical environments, size, shape, color, and location set objects apart. In the digital realm, we rely heavily on words. Words as labels. Words as links. Keywords.

Hmm, interesting. At PLANET ARGON, we have been evaluating using the rspec_for_rails plugin to focus more heavily on BDD. One of the things BDD emphasizes is leveraging the power of words to help us think more effectively (accurately) about our code. We have been incorporating BDD style method naming into our TDD practices, as well as refining our ability to specify application behavior in our tests.

One thing I dislike about the regular Test::Unit framework is that the methods are often organized ad hoc. Whatever makes the most sense to the programmer when adding those particular tests. Sometimes it’s just appending the method to the list of existing methods. Most often, I notice some sort of organization. Which makes sense; that’s how we deal with information. But the framework doesn’t provide any guidance here.

With the RSpec framework (check out RSpec on Rails), you can arrange your methods into contexts. I think this encourages better organization and contributes to the quality of findability as discussed above. Tests are just another type of information we interact with. How much effort does it take to locate what you’re looking for, understand what the tests are saying, understand the output when a test fails? Following along Jeremy’s questions about acceptable tests:

Are your tests findable?

The tangled web: data migrations

September 6th, 2006 | 0 comments

Migrations are one of my favorite things about Rails. I remember being amazed at the simplicity and usefulness when I first saw them. I also remember a previous boss asserting that we would just use SQL in the migrations because, to paraphrase, that way we would know what was happening. There’s that saying about tossing rubies before chickens, or something. But as I was saying, migrations.

One thing that makes migrations particularly useful is when they are the only means used to change the database. That way, you know the state you are in and unless you have a rare, non-reversible migration, you can migrate up and down at will. That all works great for the database structure. But what about data? We obviously won’t be funneling all the data changes through migrations. This isn’t much of an issue if your application starts as a blank slate and acquires data through use.

Recently we had an application that needed a bunch of preloaded data. Unfortunately, it wasn’t a one-shot loading of data once the structure was mostly solidified. I needed to try out stuff and wanted the super useful migrations to be my tool. Previously, this had been done with some ad hoc rake tasks, which turned out to be, frankly, a mess.

The single biggest challenge working with the data was the fact that the state of the data would be changing independent of the migrations. There was no way to ensure the state between one migration and the next.

I decided that the one thing I could assert before and after a migration was that certain records with certain id’s did not exist (pre-) and then did exist (post-migration) (reverse that for migrating down). It turned out this would be good enough for what I needed.

My first stab at implementing this was a couple helper functions that I used in migrations to update the datafiles (in yaml format) with keys for the id’s. This was a big problem because the files would be changing and were tracked in subversion, so they couldn’t really be shared by more than one developer. Playing around a bit, I came across this idea:

1
2
3
4
5
6
7
8
9
10
11
class CreateGeographicTypesFacts < ActiveRecord::Migration
  def self.up
    capture(:features, :details) do
                        # do whatever needed to create records
    end
  end

  def self.down
    release :features, :details
  end
end

Basically, as the data migration runs, I want to keep track of what records are created (and which of those are possibly destroyed). Obviously, this assumes that I’ll be adding data on the up migration and removing it on the down. To implement this, I opened up the ActiveRecord::Migration class and added a couple methods:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def self.capture(*args, &block)
  args.each do |name|
    klass = name.to_s.classify.constantize
    klass.class_eval <<-end_eval
      @@capture_ids = []
      def self.capture_ids
        @@capture_ids.uniq
      end

      after_create do |obj|
        @@capture_ids << obj.id
      end

      after_destroy do |obj|
        @@capture_ids.remove obj.id
      end
    end_eval
  end

  yield

  File.open(data_migration_file, 'w') do |f|
    f.write args.inject({}) { |hash, name|  
      klass = name.to_s.classify.constantize
      hash[name] = klass.capture_ids
      hash
    }.to_yaml
  end
end

This capture method adds callbacks for after_create and after_destroy to keep track of the id’s. This is written out to a file in a data subdirectory of the db/migrate directory. These files can be ignored by Subversion so each developer can run the migrations on their own databases without clashing.

Migrating down is basically a matter of removing the records that were created when migrating up.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def self.release(*args)
  data_file = data_migration_file(:down)
  data = File.open(data_file, 'r+') { |f| YAML.load f }
  if block_given?
    yield *args.inject([]) { |list, name| list << data[name] 
    }.compact if block_given?
  else
    args.each do |name|
      klass = name.to_s.classify.constantize
      klass.delete data[name] unless data[name].blank?
    end
  end
  File.unlink data_file
end

If you provide a block to the release method, you take care of doing whatever you want to the records. If you just want to delete them, don’t pass a block and release will delete them. I used delete rather than destroy so that everything you specify on capture is deleted without running into issues with :dependent => :destroy.

This has been useful for add and removing data while trying out different table structures. Obviously, it’s a rather targeted solution. I’ll be packaging it as a plugin and posting it to PLANET ARGON’s community site soon.