Estimation: Planning for the Unknown
For any developer—regardless of seniority—estimating the time you think a development task or project will take can be a daunting task. I was reminded of this again recently after I read software engineer @jaimeohm's heavily re-tweeted tweet about what makes "us coders" special: "We are expected to know how to do things we've never done before and estimate how long they will take." Where seniority becomes a factor is having more experience and being able to more accurately guess how long a thing will take. Of course, this isn't an issue unique to software engineering, but the problem is that people without this experience (CEOs, Project Managers, n00bs, et al [I'm generalizing here]) often just think that a coding task is just opening up a text editor and putting some text in there. It's never quite that simple: It's thinking about scale, maintenance, communicating with teams and clients, installing software, setting up environments, research, discussion with designers, user experience—all that comes with creating a product. In short, you need to estimate for the entire project and not just the "writing code" part of it.
With this in mind, it's important to not only pad your time estimates a bit, but also communicate immediately with your team or managers when a task is proving more difficult than estimated, i.e. you estimated incorrectly. You cannot worry about this effect; it will happen. You should plan for it to happen and managers should not get upset when an estimate falls short. Once you're actually working on the task, the absolute worst thing you can do is to sit frustrated with a bug or issue and not enlist the help of fellow team members or keep your team aware that you are stagnating. We all know the experience of spending hours on a tricky problem, only to solve it in 5 minutes with a fresh pair of eyes after a good night's sleep. Having a buddy take a look at your problem can often solve that immediately. Ask questions; lots of them. Absorb everyone around you's knowledge. If someone knows about a library you've been asked to use, ask them for tips. Typically, most developers understand the pain and will happy to help you estimate (seriously, hit me up on AIM any time if you want to chat).
Now, padding time might sound like a negative thing (especially to your clients), but, in the end, you're setting a budget expectation, so when you do end up completing that feature more quickly than expected, your producer is happy, your client is happy and you've potentially made your company some profit. Don't forget than some features will take longer than expected so now you may have time to work on another feature that you underestimated or add that really kick-ass feature to surprise and delight your team and client.
I thought I'd share a recent example of how coding projects are often less about code and more about the things you don't estimate and the things most people don't see. Below you'll find a sample from a project log I wrote during the development of a Ruby on Rails application. I had done some Rails when it was version ~0.9, but had not touched it in it's modern incarnation (which was 3.0.9 when this was written), so I was basically starting from scratch. For the budget, I estimated based on the time that something like this would have taken me to produce in PHP (the language I use the most frequently) and added a bit of extra time for the unknown. You can see in this log that 90% of the work I'm doing has absolutely nothing to do with writing the code. I of course didn't expect to run into *any* of the issues in the log since I had never done it before, but now with this experience, I know to factor in some extra time to get my environments all ready to actually get to the "writing code" stage or I can estimate lower since I know how it works now.
Day 1
Going to be building an API using Ruby on Rails and deploying it on Heroku. First I’ve established that Heroku uses PostgreSQL, not MySQL, but shouldn’t be a big deal because of Rails’ database abstraction capabilities. Locally, I'm going to be lazy and use the MySQL installation provided by MAMP. Let’s get started.
First start updating Ruby gems.
gem update --system
Then I updated my system’s gems:
gem update
Rails was updated to version 3.0.9. Next created a rails app:
rails new app --database=mysql
Next installed the rails bundle:
bundle install
Hmm, failed hard. Needed to install mysql2 gem to make this work, but this didn’t work:
sudo gem install mysql2
Ok, so tried with explicitly setting the path to mysql_config:
gem install mysql2 -- --with-mysql-config=/Applications/MAMP/Library/bin/mysql_config
No dice. Forgot that the default MAMP install doesn’t have MySQL’s header files, so this won’t work. Decided to install a fresh copy of MySQL to do this instead. Used Homebrew to install it. Thankfully I’ve already got the Xcode developer tools (which Homebrew requires) so I don’t have to spend 4 hours downloading and installing those).
brew install mysql
OK, that took about 15 minutes. MySQL is installed in
/usr/local/Cellar/mysql/5.5.14
So let’s try this again:
gem install mysql2 -- --with-mysql-config=/usr/local/Cellar/mysql/5.5.14/bin/mysql_config Building native extensions. This could take a while... Successfully installed mysql2-0.3.6 1 gem installed
Boom, hell yeah. Let’s try this install again inside the rails project:
bundle install
Everything’s installed. Fantastic. Edited my database configuration and tried
rake db:create
No dice. Better add the path to the UNIX Socket to the config to both development and test configurations:
socket: /Applications/MAMP/tmp/mysql/mysql.sock
Now db:create works and I’ve got two databases.
Starts writing code...
Day 2
After some time doing research on MongoDB and document-based databases in general, I've decided to offload some of the writing tasks for this project to a database on MongoHQ. So I'm going to install Mongo locally first.
brew install mongodb
Well, that was easy.
Added mongoid to my gemfile:
gem 'bson_ext', '~> 1.3' gem 'mongoid', '~> 2.1'
bundle install to get up to date
then rails task to generate a config file:
rails generate mongoid:config
Writing code...
Day 3
I wanted to use bigints for some of my records, so after researching how Rails defines the default field types in databases. I added this to config/environment.rb as a new database type:
require 'active_record/connection_adapters/mysql2_adapter' require 'active_record/connection_adapters/postgresql_adapter' ActiveRecord::ConnectionAdapters::Mysql2Adapter::NATIVE_DATABASE_TYPES[:big_primary_key] = "BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY".freeze ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:big_primary_key] = "bigserial primary key".freeze
Added a few config items to config/application.rb to get ORM rake tasks working after installing Mongoid:
# In order to properly set up single collection inheritance, # Mongoid needs to preload all models before every request in development mode. config.mongoid.preload_models = true if Rails.env.development? # mongoid has overridden activerecord as the default orm so any attempts # to generate migrations or ActiveRecord models will fail. Therefore we # must configure our default generators so we can still use activerecord. config.generators do |g| g.orm :active_record end
Today I installed haml gem and set up the front end of the site. Super easy and HAML is pretty amazing! Only took about an hour. Researched how to create custom rake tasks and made this frivolous task in lib/tasks/database.rake:
namespace :db do
desc 'Drop and recreate the database and reseed it.'
task :reboot => [:drop, :create, :migrate, :seed] do
puts 'Database rebooted.'
end
desc 'Recreate the database with a blank slate.'
task :clean => [:drop, :create, :migrate] do
puts 'Database cleaned.'
end
end
How do you estimate? Let's talk about it on Twitter @typeoneerror.