To Rewrite or Not to Rewrite?

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
30
31
32
33
34
35
To rewrite, or not to rewrite- that is the question: 
Whether 'tis better for the product to suffer
The features and debt of outrageous history
Or to once again battle a sea of edge cases,
And by forgetting relive them. To wish- to hope-
No more; and by hope to say we end
The heartache, and the thousand unnatural cases
That code can error to. 'Tis a codebase
Devoutly to be wish'd. To wish- to hope.
To hope- perchance to rebuild: ay, there's the rub!
For in that hope of clarity what simplicity comes
When we have removed this outdated cruft
Must give us success. But give the respect
To the current repo of such long life.
For who would bear the features of the past,
High expectations, the race conditions,
The admin tools, the product delay,
The exhaustion overcome, and the data
That shall posthaste be moved to a new store
As each mistake of the past be brought back
With sighs of regret? Who would these issues bear,
To toil and code under a weary life,
But that the chance of something rebuilt
That undiscover'd codebase, from whose lines
No complexity returns- tempts the will,
And makes us choose between those ills we have
Than sprint towards others we know not of?
Thus the unknowns make cowards of us all,
And thus the heavy weight of such choice
Is oft tempered by promises of thought,
And refactorings of great scope and breadth
With this regard the hope does turn awry
And lose the name of action.- What say you?
The lauded pivot! Siren of opportunity
May all our sins be forgotten.

The internal struggle of the rewrite decision eats away at developers. It could be so much better. We have learned so much. Let’s start over. It causes inaction over months accompanied by much grumbling. But if you do it, how can you make sure it doesn’t turn into a tragedy?

I can’t say that I am happy or proud that we have rewritten TaskRabbit twice. That doesn’t feel right. Conceptually, if we would have done it correctly the first time, then it wouldn’t have been needed. Or maybe we should have done it in place. I would say that’s absolutely fair, but doesn’t capture the reality of development of the last 6 years.

When I started writing this post, I just felt like mapping my existential crisis to Hamlet’s and now I’m heading towards defending myself against Joel’s famous post stating the fact that you should never do a rewrite. I just went back and read it (again) and I (still) agree. It’s hard to argue with. Maybe it’s best to discuss the times we did rewrite to the times we didn’t.

Read on →

Building React Native Apps

We’ve been using and loving React Native as noted in my previous post. As we are working towards rolling out a fully-featured app, one thing that needed solved was how we should build the app for different environments. For example, how can we make (slightly different) development, staging, and production builds?

In a Github issue, I ran into a few other people also wondering how to do this, so I’ve added a few ways to the Example App to show the approaches we are using.

The three approaches we are trying out are:

  • Configurations
  • Compile Flags
  • Run Variables

Environments

I had already added the Environment model and EnvironmentStore store to the project. However, I just added actual dynamic configurations to the XCode project and the EnvironmentManager code is using that.

Read on →

V2 - Retrospective

TaskRabbit began as RunMyErrand in 2008 when Leah had the idea and coded up the first version of the site. In 2009, I had the opportunity to help out in little ways like adding Facebook Connect support just after it launched and Leah got into Facebook Fund. From there, she raised a seed round and I came on full-time.

For a few weeks after starting, I worked on the RunMyErrand codebase, adding features and fixing bugs. Quickly, though, a few things became clear. First, we were probably going to change our name. RunMyErrand made people think only about laundry. Second, the changes we wanted to make drastic and hard to make with confidence in a codebase with no tests. I was hoping to work and live with this code for several years and we did not have the foundation that would make that a productive and enjoyable experience.

So around Christmas 2009, I started a new Rails project. It was still called runmyerrand because we still didn’t have a new name. For a while at the end we called it core because it was at the center of a large service-oriented architecture. Today, we call it V2 because it has now itself been replaced.

It’s been a year and half. It’s never too late for a retrospective.

Launch

The original site was my first Rails project to work on and V2 was my first one from scratch. Rails 3 wasn’t yet released so I was nervous to get on that bleeding edge because most of the gems didn’t work quite yet. I had been immersing myself in Ruby news. In particular, I’d been listening to Ruby5 and others podcasts and been taking notes about gems/tools that seemed relevant. In hindsight and with experience, it was a problem to rely on gems for fairly simple things, but at the time they seemed sent from heaven to solve my problems.

I started over Christmas at the very beginning.

The site was black and white with a simple layout. At some point in January, Leah saw what I was working on. I, of course, discussed with her the notion of rebuilding the site, but I don’t think the ramifications quite came across until she saw the starkness of that layout. It was probably a huge leap of faith for her at that moment to have the trust in me that she did.

I worked on both sites through January and February, eventually getting to 100% on new stuff. For the most part, I was building a feature-complete version of RunMyErrand with TBD branding and stronger Rails conventions like skinny controllers and tests. There were some new features and many minor upgrades from the learnings we’d had.

By the end of April, it was about ready to go. We had picked a name, gotten help from designers and Dan, a great contractor to pull it over the finish line. In one hour on April 5th, we launched the new code and rebranded the company.

Read on →

React Native Example App: Navigation

At TaskRabbit, we have been looking into building an iOS application in React Native. This is probably the first pure technology that I’ve been this excited about since moving from C to Ruby.

However, it’s definitely still in its early days. There are not many examples of how people are doing things out there. To help remedy this and share what we are learning, I made a sample React Native application.

The app itself is vaguely like twitter and/or tumblr. There are users that make posts. They follow other users. You can look at users they follow follows and those users’ posts. And on and on! The features (or styling) isn’t the main point. At this time, we’re mostly demonstrating architectural concepts.

The app we’re working on is a bit ahead of this one, but I think it will be neat to have this one publicly walk through the same steps that we have done privately. Everything is pretty new and the patterns are not established. We’ll post here about some pattern or refactor and update the app and hopefully start a great conversation.

Navigation

The first pattern that I wanted to talk about is navigation. The web has a pretty solid navigation story (with the URLs and such) and some tools to map that to React applications. I think it’s less clear on the phone.

I’m not sure why it’s different, actually, because there is still usually a “Back” button and one screen at a time. Yet, iOS development seems to have evolved in another direction. Most apps are all about this NavigationController and we push and pop and stuff like that. Then things get totally weird when we try to put the URLs back in for something like deep linking.

Read on →

Teaching CSS

To my surprise, my daughter asked me if I would teach her how to build a web site today. She more or less wants a blog to keep track of what is going on at a recess club she made at school: regular notes, calendar, things like that.

I had her draw out on paper what she wanted to build. It was interesting to see her reinvent the concept of a blog. For example, her first iteration only showed the post from “Today.” I asked her how you would see yesterday’s post. So she add a “Today” and a “Yesterday” button. So I asked her how to see the post from last week. Her answer was approximately, “Well, they just need to keep up!” However, she eventually conceded to a “Previous” and “Next” button.

Even on one’s first encounter, it seems that it’s impossible to teach this stuff without also teaching about concessions:

Daughter: OK, here’s a picture of what I want to make (shows few buttons on top, content underneath)

Dad: (Talks about closing and opening tags, gets a word on the page)

Daughter: Make it bigger.

Dad: (adds h1 tag)

Daughter: Add the other buttons.

Dad: (adds two more h1 tags)

Daughter: Why are they not next to each other?

Dad: (mumbles about ul and li - makes them a list)

Daughter: Now they are too small again and have those dots.

Dad: We need to “style” them. Let’s remove the dots. (inlines styles: list-style-type:none;)

Daughter: OK. Now make it bigger again.

Dad: (adds font-size:50px; to the first one) There.

Daughter: What about the other ones?

Dad: How do you think we make them bigger too?

Daughter: (eventually adds inline style to other ones). Perfect! Now put them next to each other.

Dad: (adds display:inline;)

Daughter: You’re amazing! Ok, now make the background gold.

Dad: (background-color:Gold)

Daughter: That’s yellow.

Dad: Here, look at this.

Daughter: Wow, so many choices! (picks GoldenRod)

Dad: (shows how to change to that)

Daughter: Perfect! Now make the buttons this one (points to DarkTorquise)

Dad: (adds links makes them background-color:DarkTurquoise;padding: 10px;)

Daughter: OK, let’s add the main words. In a white box.

Dad: (adds div with background-color:White; and a few words)

Daughter: Why is it so small?

Dad: We need more words. (pastes Lorem Ipsum)

Daughter: (not impressed) It needs to be bigger and in the middle.

Dad: (adds padding:20px;margin-left:auto;margin-right:auto; to horizontally center content) How about that?

Daughter: Also in the middle between the top and bottom.

Dad: (sighs heavily. googles “vertical center div css” for the 100th time. reads for 7 minutes.)

Daughter: What’s wrong?

Dad: Are you sure you don’t want it near the top?

OKRs

At TaskRabbit, we have something called Objectives and Key Results or OKRs for short. Many companies do the same.

We’ve always talked about them as one thing. As in, “What’s the OKR?” That would more or less mean, “What number are we trying to hit?” An example would be getting better at fulfilling tasks by focusing on moving up the percentage of tasks that are posted and get successfully completed.

This metrics-driven approach can really work but its efficacy seems related to the metric chosen. If the team believes it’s the right number to move, it works out. If they see weird loopholes or tricks, then it is less effective. Because of this, there is much discussion each time about the best metric to focus upon. Assuming we don’t focus on something all-encompassing (“Revenue”), it often has turned out to be very difficult to find exactly the right metric.

My recent realization is that there are objectives and key results. Of course, that’s obvious because it’s the name of the thing. However, in my mind, I don’t think I ever separated them to the degree that they deserve to be separated. It was always about hitting the metric and that was the objective. The goal, as I now understand it, should be to make the objective more of the “intent” of the situation. The key result is simply a way to “sample” that intent.

Example

As an example, we could have an objective of “making TaskRabbit a habit.” I think it’s helpful to make it completely about the intent and not something like “increasing lifetime value” or “doubling monthly active users” or whatever. That helps when making decisions throughout the quarter and is open to interpretation as the understanding of it evolves. The key result, then, is for sampling the progress. It could be “increase the average number of tasks per month to X” or “get Y% more people to their 4th completed task,” or “increase the average number of consecutive weeks of usage to Z” or any number of other options.

Each metric will have its holes, but the best metrics will be the one or two that:

  • are highly correlated to the intent of the objective
  • can be sampled frequently to understand and measure progress
  • are easily explainable and understandable to everyone

This produces a metric that doesn’t have to have its nuances explained every time we talk about it, can be on the wall in dashboard form to see if it’s going well, and that we believe is a good enough sample of the goal.

Read on →

Big Decimal

At TaskRabbit, people have hourly rates for the tasks that they do. They get paid based on the number of hours that they work.

We try to deal in base units to remove ambiguity. For example, we store the hourly rate in cents as an integer instead of in dollars as a double. Reporting time worked occurs in seconds. When that reporting occurs, there is a calculation to do to understand how much the Tasker needs to be paid. It might go something like this:

1
2
3
4
5
6
7
8
9
10
11
12
class FloatCalculator
  def initialize(hourly_rate_in_cents)
    @hourly_rate_in_cents = hourly_rate_in_cents
  end

  def total_in_dollars(seconds_worked)
    hours = seconds_worked / 3600.0 # seconds in an hour
    cents = @hourly_rate_in_cents * hours
    dollars = cents / 100 # make it dollars
    dollars.round(2)
  end
end

Over the years, we saw a few errors because of limitations of floating point math. Since then, we have switched to using BigDecimal to prevent these kinds of errors.

There were no visible performance issues, but I recently got curious because all of the docs say that they are substantial. By writing the same calculator using BigDecimal, we can see the difference.

1
2
3
4
5
6
7
8
9
10
11
12
class BigDecimalCalculator
  def initialize(hourly_rate_in_cents)
    @hourly_rate_in_cents = BigDecimal.new(hourly_rate_in_cents)
  end

  def total_in_dollars(seconds_worked)
    hours = BigDecimal.new(seconds_worked) / BigDecimal.new(3600)
    cents = @hourly_rate_in_cents * BigDecimal.new(hours)
    dollars = cents / BigDecimal.new(100)
    dollars.round(2).to_f
  end
end

This does turn out to be significantly slower, so I also benchmarked marking the constants class level variables.

Read on →

Queue Bus

At TaskRabbit, we have been using resque-bus for about two years. It has continued to provide value by linking components via a loosely coupled publish/subscription model. We have seen 10x the number of events going through it, but have not yet hit any scaling issues. The benefit of using the tools we already have continues to be a huge win.

But other teams are using other tools like Sidekiq. We’re also interested in trying it out, but resque-bus (unsurprisingly) was tied closely to Resque. We’ve changed that by creating queue-bus and using an adapter pattern. There are now adapters in resque-bus and sidekiq-bus, as well as a compatible version in node-queue-bus.

The code interactions are basically the same but can work across systems. You can even have one app using Resque and one using Sidekiq.

Pick your adapter

By requiring one of the adapters, it automatically gets set up to be the one for the app.

1
require 'sidekiq-bus' # (or 'resque-bus')

Application A publishes an event

Something happens in your application and you want to let the world know. In this case, you publish an event.

1
2
3
4
5
# business logic
QueueBus.publish("user_created", id: 42, first_name: "John", last_name: "Smith")

# or do it later
QueueBus.publish_at(1.hour.from_now, "user_created", id: 42, first_name: "John", last_name: "Smith")

Application B subscribes to events

If the same or different application is interested when an event happens, it subscribes to it by name.

1
2
3
4
5
6
7
# initializer
QueueBus.dispatch("app_b") do
  subscribe "user_created" do |attributes|
    # business logic
    NameCount.find_or_create_by_name(attributes["last_name"]).increment!
  end
end

Upgrading

The formats changed a little bit with the move. The last version that used the old format (0.3.7) also can adapt to the new format. This is important because you’ll have things in the queue during the transition.

Steps:

  • Upgrade everyone to 0.3.7
  • Deploy all the things
  • Upgrade everyone to the newest version

More to come

If people continue to like this approach and gem, we have lots of approaches and tools built on top of it that we’d be excited to make available. Let us know on Github that you like it by watching, starring, or creating issues with questions, etc.

Now that we have the adapter pattern, also let us know if you are interested in making one for your background system of choice. A special thanks goes out to Jonathan Greenberg and the team at purpose.com who did just that to get this whole thing going.

Apple TV Dashboard

So you have all these screens around and you want to have something awesome like on those one that Panic made. You may also already have Apple TVs on these TVs because AirPlay is an effective way to project screens in an office full of Macs. This is position we were in at TaskRabbit and we’d tried a few things.

The most recent iteration was to have one Mac Mini in a closet and lots of very long wires into the TVs. It displayed a web page that cycled through the content. This wasn’t bad, but had a few issues. First, the screens all had to show the same thing. I wanted to be able to have different content on each. For example, in addition to key metrics, the conference rooms would have who had the room booked or the one by customer support would have more data on the call volume. But it just isn’t worth it to have 10 Mac Minis for this purpose. The other issue is that people still wanted to use Airplay, so they would switch the input and then not switch it back to the ambient content.

The solution seemed clear. I needed to get the dashboards to be the screensaver of an Apple TV.

TLDR

Here is a sample project to ties all of this up into one package.

It uses the new gems, dashing-screenshots and icloud-photo, to take screenshots and upload them to iCloud. These then show up on an Apple TV

Sample dashboard on the wall

(not actual dashboard)

To make this happen, check out the project and follow the instructions there.

Dashboard

Looking around at various dashboard apps and having made a few custom solutions, I settled on Dashing this time. It looked nice by default, had a great model for adding custom content, and is written in the language we tend to use around here (Ruby).

The Dashing docs are quite good so I won’t go into detail of how we pulled in our data. The samples are actually the best as they show a simple use case of the pattern of how to add jobs. I made some helpers to be able to pull in data from Looker. By putting the actual data in Looker, I’m hoping it will allow others to maintain the metrics definitions. This will hopefully keep the dashboard up to date and relevant. An issue we’ve had in the past is that the SQL became somewhat stale.

Screenshots

I’d like a fully dynamic dashboard as much as the next guy, but the screensaver of an Apple TV consists only of images. Therefore, the next step was to get a screenshot of all of the dashboards that Dashing has to offer. I have worked on a similar project within our test suite and had great success with Selenium. I added selenium-webdriver to the Gemfile and this file to lib.

Read on →

Translating Rails Fields

When we launched TaskRabbit in London, one of our goals was to have a fully localized product. There are enough differences between British English and US English to not cut (m)any corners. Of course, it’s even more important in completely different languages.

An issue that I noticed one day was in our signup flow. It said “Last name is required.” This was caused by a blank field and a model-level validation.

1
2
3
class User
  validates :last_name, presence: true
end

It was working as expected, but it should have said “Surname is required.” That’s what they want across the pond.

We translate all of our “en” locale into “en-GB” but this wasn’t there because it more or less works automatically. Active Model does a humanize call on the field name. To translate this field, which we had done on stranger field names, you add it to a YML locale file.

1
2
3
4
5
en:
  activerecord:
    attributes:
      user:
        last_name: 'Last Name'

This is for the one model, but there is fallback code in Active Model that also allows you to define the name for all models. You can do this:

1
2
3
en:
  attributes:
    last_name: 'Last Name'

This has the advantage of also working for another model as well as an Active Model service objects in one shot.

Read on →
Copyright © 2017 Brian Leonard