SlideShare ist ein Scribd-Unternehmen logo
1 von 83
Startup Ruby

Separate things that change from things
          that stay the same

    Program to an interface, not an
           implementation

 Prefer composition over inheritance

     Delegate, delegate, delegate

  You Ain't Gonna Need It (YAGNI)
I’m Mike Subelsky
    @subelsky
I’m Mike Subelsky
            @subelsky


This is what I have learned while building
What’s unique about
        startup programming?
Facing unknown problems & unknown solutions


                     With scarce time and
                          resources
“Ferocious customer-centric
      rapid iteration”

                        @ericries
               startuplessonslearned.com
What advice would I give myself
 to thrive in these conditions?
Design thoughtfully before implementing

  Don’t make these specific mistakes
Separate things that change from things
           that stay the same

Program to interfaces, not implementations

   Prefer composition over inheritance

       Delegate, delegate, delegate

    You Ain't Gonna Need It (YAGNI)
Design for Change
"Separate code for general functionality from
     code for specialized functionality"

Isolate design decisions in their own modules
First Use of SQS
 SQS =
 RightAws::SqsGen2.new(access_key_i
 d, secret_access_key], { :multi_thread
 => true })
queue =
SQS.queue("#{RAILS_ENV}_#{queue_name
}")
First Use of SQS
 SQS =
 RightAws::SqsGen2.new(access_key_i
 d, secret_access_key], { :multi_thread
 => true })
queue =
SQS.queue("#{RAILS_ENV}_#{queue_name
}")

    Then both of these changed
Designed for Change
queue =
QueueFetcher.fetch(queue_name)

queue.send_message({ :user_id =>
user_id }.to_yaml

 Isolates how we connect to the messaging system

Isolates naming convention for the messaging system
class QueueFetcher

 def self.fetch(queue_name)
  SQS2.queue("#{queue_env}_#{queue_name}")
 end

 private

 def self.queue_env
  APP_CONFIG['queue_context']
 end

end
class QueueFetcher

 def self.fetch(queue_name)
  SQS2.queue("#{queue_env}_#{queue_name}")
 end

 private

 def self.queue_env
  APP_CONFIG['queue_context']
 end

end
      More isolation
I don’t like this
class QueueFetcher

 def self.fetch(queue_name)
  SQS2.queue("#{queue_env}_#{queue_name}")
 end

 private

 def self.queue_env
  APP_CONFIG['queue_context']
 end

end
      More isolation
Business Logic
class Envelope

 def deliver_first_message
  new_mailbox_name = Mailbox.new_mailbox_name(recipient)
  mailbox = Mailbox.create!(:user_id => user_id, :name => new_mailbox_name)

  prepare_delivery_for_spam
  prepare_delivery_for_forwarding(mailbox)

  self.first_message = true
  return deliver(mailbox)
 end
Program to
    interfaces, not
   implementations

   Program to general types
"Don’t call it a car if you can get
 away with calling it a vehicle"
Program to
     interfaces, not
    implementations

Easy for us to do with duck-typing
class Message < ActiveRecord::Base
end
class ArticleMessage < Message
end
class SmtpMessage < Message
end
class SentMessage < Message
end
class Message < ActiveRecord::Base
end




               ?
class ArticleMessage < Message
end
class SmtpMessage < Message
end
class SentMessage < Message
end
Prefer composition
   over inheritance
Equip objects with references to
other objects which implement
       common behavior
module S3MessageContent

 private

 def head_from_s3(filename)

S3.head(APP_CONFIG['message_bucket_name'],filename)
 end

end
class Attachment < ActiveRecord::Base

 include S3MessageContent

 belongs_to :message
 before_create :put_attachment_on_s3
 before_destroy :remove_from_s3

end
Delegate, delegate,
          delegate
       Objects express certain
         outward behavior

but actually delegate responsibility for
   that behavior to another object
Delegate, delegate,
     delegate
Really good for ActiveRecord
         relationships
Delegate, delegate,
             delegate
       Really good for ActiveRecord
                relationships
class ExternalEmailAccount < ActiveRecord::Base
 belongs_to :external_email_server

 delegate :server_info, :mail_server,
      :to => :external_email_server
Delegate, delegate,
        delegate

Forwardable and Delegate modules
       also make this easy
YAGNI
YAGNI

We love solving cool, new problems
       with cool, new toys
YAGNI

We love solving cool, new problems
       with cool, new toys

So sometimes we look into the future
         for opportunities
YAGNI

This is a fatal instinct in startups
YAGNI
 I’ve built things to be super-scalable that
turned out not to be core to the product
YAGNI
 I’ve built things to be super-scalable that
turned out not to be core to the product
YAGNI
In the early days, focus on
learning not performance*
YAGNI
  In the early days, focus on
  learning not performance*

Concentrate on 80% solutions
YAGNI
  In the early days, focus on
  learning not performance*

Concentrate on 80% solutions

 *startuplessonslearned.com
YAGNI
Be a duct tape programmer
YAGNI
  Be a duct tape programmer
“...any kind of coding technique that’s
even slightly complicated is going to
doom your project.”

 http://www.joelonsoftware.com/items/
           2009/09/23.html
Don’t make these specific mistakes
Plan to move
everything out of the
    web request
Plan to move
everything out of the
    web request
  ar_mailer, delayed_job,
    EventMachine, SQS,
      beanstalkd, etc.
Plan to move
everything out of the
    web request
  But remember YAGNI
Make careful use of
  concurrency
Make careful use of
    concurrency
Prefer processes communicating
        via message bus
   (SQS, Starling, delayed_job,
        Rabbit MQ, etc.)
Make careful use of
     concurrency

        Check out Unicorn
http://tomayko.com/writings/unicorn-is-unix
Make careful use of
       concurrency
Threading: EventMachine is your friend
Make careful use of
          concurrency
Threading: EventMachine is your friend
EMH.safe_defer do
 begin
  UserMailer.deliver_verification_email(@user, @email)
 rescue StandardError
  logger.warn("Unable to deliver signup verification
  to #{@user.login} due to #{$!.message}")
 end
end
Consider your RDBMS
    relationship
Consider your RDBMS
      relationship
Avoid touching the DB when storing
         non-critical data
Consider your RDBMS
      relationship
Avoid touching the DB when storing
         non-critical data
     Don’t use an RDBMS for
      things it’s not good at
Consider your RDBMS
      relationship
Avoid touching the DB when storing
         non-critical data
     Don’t use an RDBMS for
      things it’s not good at

     We rely heavily on AWS
Consider your RDBMS
    relationship
 Storing large text blobs (S3)
   Messaging system (SQS)
 Logging events (SimpleDB)
 Caching dynamic text (S3)
Consider your RDBMS
    relationship
We use data_fabric gem to make
   master-slave transparent
Consider your RDBMS
    relationship
   We also just used it
    to shard our DB
default: &default
 adapter: 'mysql'
 username: otherinbox_mysql
 database: otherinbox_production      shard_0: &shard_0
 password: ---------                   <<: *controller
 encoding: utf8
 pool: 15                             shard_0_slave: &shard_0_slave
                                       <<: *controller_slave
controller: &controller
 <<: *default                         <% 1.upto(10) do |n| %>
 host: #####                          <%= "shard_#{n}: &shard_#{n}" %>
                                      <%= " <<: *default" %>
controller_slave: &controller_slave   <%= " host: shard#{n}.####" %>
 <<: *default                         <%= %>
 username: otherinbox_ro              <%= "shard_#{n}_slave: &shard_#{n}_slave" %>
 host: ####                           <%= " <<: *shard_#{n}" %>
                                      <% end %>

                                      # production!
                                      production:
                                       <<: *controller

                                      <% 0.upto(10) do |n| %>
                                      <%= "shard_#{n}_production:" %>
                                      <%= " <<: *shard_#{n}" %>
                                      <% end %>
Great DB
    Scaling Videos

Scaling Your DB Part 1 and 2:
 http://railslab.newrelic.com
Reconsider
Virtualization
DB indexes degrade
      over time
Everyone blogs about EXPLAIN
 but what about ANALYZE and
         OPTIMIZE?
Know Your
      Query Planner
One of our biggest speedups came
from upgrading to the latest minor
         MySQL version
Organize your
      code nicely

We have too much
  code in lib/*
Organize your
          code nicely
  A lot of this stuff is
       plumbing
and could be extracted
  as plugins or gems
Organize your
          code nicely
  A lot of this stuff is
       plumbing
and could be extracted
  as plugins or gems

  And released open
       source!
Organize your
        code nicely
 ActiveMerchant
has a great layout
Organize your
   con g variables
Dangerous / difficult to change
      Rarely changing
   Changeable at runtime
Dangerous / difficult
    to change
  INBOX_MESSAGE = 1
  ARCHIVED_MESSAGE = 2
  DELETED_MESSAGE = 3
  SENT_MESSAGE = 4
  REJECTED_MESSAGE = 5
Nearly immutable, identical
     in all situations
Rarely changing
default: &default
 full_host_name: 'my.otherinbox.com'
 tech_support_address: 'support@otherinbox.com'
 max_subdomains_per_user: 2
 max_alternate_domain_name_results: 10
 domain_registration_api_timeout: 10


          Dump into a YAML file
Rarely changing

    development:
     <<: *default
     full_host_name: 'oib.local'
     domain_registration_api_timeout: 1



You want it under version control
Changeable at
             Runtime
>> Configuration.default_blocked_addresses
=> "admin,support,help,info,sales,jobs,webmaster"

>> Configuration.default_blocked_addresses += ",oib"
=>
"admin,support,help,info,sales,jobs,webmaster,oib"
Changeable at
             Runtime
>> Configuration.default_blocked_addresses
=> "admin,support,help,info,sales,jobs,webmaster"

>> Configuration.default_blocked_addresses += ",oib"
=>
"admin,support,help,info,sales,jobs,webmaster,oib"

     http://beautifulpixel.com/svn/plugins/settings
Changeable at
             Runtime
>> Configuration.default_blocked_addresses
=> "admin,support,help,info,sales,jobs,webmaster"

>> Configuration.default_blocked_addresses += ",oib"
=>
"admin,support,help,info,sales,jobs,webmaster,oib"

     http://beautifulpixel.com/svn/plugins/settings
            http://toolmantim.com/articles/
         consolidating_your_apps_constants
Avoid Boolean
        Columns
 Often want to know what time
      something changed
Or you later end up needing more
          than 2 states
Bundle complex view
logic into Presenters
Bundle complex view
         logic into Presenters
class RefreshController < ApplicationController

 before_filter :signin_required

 def index
  render :text => JSON.generate(AdvancedRefresher.new(params).to_hash)
 end

end
Maybe don't test all
 the time, at the
   beginning?
Maybe don't test all
 the time, at the
   beginning?
Maybe don't test all
 the time, at the
   beginning?
Can slow down exploratory
       programming
Maybe don't test all
   the time, at the
     beginning?
   You’ll probably throw away
           half the stuff
you write at the beginning anyway
Maybe don't test all
 the time, at the
   beginning?
 You’ll definitely change
  the names of things!
Maybe don't test all
  the time, at the
    beginning?
 On the other hand, tests can
be a design tool (as with BDD)

I wrote our SMTP code this way
Maybe don't test all
    the time, at the
      beginning?
Would be interesting to see how
many Rails Rumble teams use tests
Afford regular
access to designers
Questions?
 @subelsky
mike@oib.com
Thank you!
Slides posted at subelsky.com

Weitere ähnliche Inhalte

Was ist angesagt?

Symfony Guard Authentication: Fun with API Token, Social Login, JWT and more
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and moreSymfony Guard Authentication: Fun with API Token, Social Login, JWT and more
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and moreRyan Weaver
 
Symfony & Javascript. Combining the best of two worlds
Symfony & Javascript. Combining the best of two worldsSymfony & Javascript. Combining the best of two worlds
Symfony & Javascript. Combining the best of two worldsIgnacio Martín
 
Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)Ryan Weaver
 
The Complementarity of React and Web Components
The Complementarity of React and Web ComponentsThe Complementarity of React and Web Components
The Complementarity of React and Web ComponentsAndrew Rota
 
Javascript REST with Jester
Javascript REST with JesterJavascript REST with Jester
Javascript REST with JesterMike Bailey
 
Introduction to Vue.js
Introduction to Vue.jsIntroduction to Vue.js
Introduction to Vue.jsMeir Rotstein
 
Html5 and beyond the next generation of mobile web applications - Touch Tou...
Html5 and beyond   the next generation of mobile web applications - Touch Tou...Html5 and beyond   the next generation of mobile web applications - Touch Tou...
Html5 and beyond the next generation of mobile web applications - Touch Tou...RIA RUI Society
 
Guard Authentication: Powerful, Beautiful Security
Guard Authentication: Powerful, Beautiful SecurityGuard Authentication: Powerful, Beautiful Security
Guard Authentication: Powerful, Beautiful SecurityRyan Weaver
 
The Next Five Years of Rails
The Next Five Years of RailsThe Next Five Years of Rails
The Next Five Years of RailsAlex Mercer
 
React && React Native workshop
React && React Native workshopReact && React Native workshop
React && React Native workshopStacy Goh
 
Rails 3.1 Asset Pipeline
Rails 3.1 Asset PipelineRails 3.1 Asset Pipeline
Rails 3.1 Asset Pipelineeallam
 
How to Build ToDo App with Vue 3 + TypeScript
How to Build ToDo App with Vue 3 + TypeScriptHow to Build ToDo App with Vue 3 + TypeScript
How to Build ToDo App with Vue 3 + TypeScriptKaty Slemon
 
Apache Wicket Web Framework
Apache Wicket Web FrameworkApache Wicket Web Framework
Apache Wicket Web FrameworkLuther Baker
 
Building Single Page Apps with React.JS
Building Single Page Apps with React.JSBuilding Single Page Apps with React.JS
Building Single Page Apps with React.JSVagmi Mudumbai
 

Was ist angesagt? (20)

The road to Ember 2.0
The road to Ember 2.0The road to Ember 2.0
The road to Ember 2.0
 
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and more
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and moreSymfony Guard Authentication: Fun with API Token, Social Login, JWT and more
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and more
 
Dan Webb Presentation
Dan Webb PresentationDan Webb Presentation
Dan Webb Presentation
 
Symfony & Javascript. Combining the best of two worlds
Symfony & Javascript. Combining the best of two worldsSymfony & Javascript. Combining the best of two worlds
Symfony & Javascript. Combining the best of two worlds
 
Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)
 
The Complementarity of React and Web Components
The Complementarity of React and Web ComponentsThe Complementarity of React and Web Components
The Complementarity of React and Web Components
 
Javascript REST with Jester
Javascript REST with JesterJavascript REST with Jester
Javascript REST with Jester
 
Introduction to Vue.js
Introduction to Vue.jsIntroduction to Vue.js
Introduction to Vue.js
 
learning react
learning reactlearning react
learning react
 
Wicket 2010
Wicket 2010Wicket 2010
Wicket 2010
 
Html5 and beyond the next generation of mobile web applications - Touch Tou...
Html5 and beyond   the next generation of mobile web applications - Touch Tou...Html5 and beyond   the next generation of mobile web applications - Touch Tou...
Html5 and beyond the next generation of mobile web applications - Touch Tou...
 
Guard Authentication: Powerful, Beautiful Security
Guard Authentication: Powerful, Beautiful SecurityGuard Authentication: Powerful, Beautiful Security
Guard Authentication: Powerful, Beautiful Security
 
WCLV13 JavaScript
WCLV13 JavaScriptWCLV13 JavaScript
WCLV13 JavaScript
 
The Next Five Years of Rails
The Next Five Years of RailsThe Next Five Years of Rails
The Next Five Years of Rails
 
React && React Native workshop
React && React Native workshopReact && React Native workshop
React && React Native workshop
 
Rails 3.1 Asset Pipeline
Rails 3.1 Asset PipelineRails 3.1 Asset Pipeline
Rails 3.1 Asset Pipeline
 
Hooks WCSD12
Hooks WCSD12Hooks WCSD12
Hooks WCSD12
 
How to Build ToDo App with Vue 3 + TypeScript
How to Build ToDo App with Vue 3 + TypeScriptHow to Build ToDo App with Vue 3 + TypeScript
How to Build ToDo App with Vue 3 + TypeScript
 
Apache Wicket Web Framework
Apache Wicket Web FrameworkApache Wicket Web Framework
Apache Wicket Web Framework
 
Building Single Page Apps with React.JS
Building Single Page Apps with React.JSBuilding Single Page Apps with React.JS
Building Single Page Apps with React.JS
 

Ähnlich wie Ruby For Startups

Intro to Ruby on Rails
Intro to Ruby on RailsIntro to Ruby on Rails
Intro to Ruby on RailsMark Menard
 
Intro to-rails-webperf
Intro to-rails-webperfIntro to-rails-webperf
Intro to-rails-webperfNew Relic
 
Osiąganie mądrej architektury z Symfony2
Osiąganie mądrej architektury z Symfony2 Osiąganie mądrej architektury z Symfony2
Osiąganie mądrej architektury z Symfony2 3camp
 
Developing faster than ever (Liferay DEVCON 2017)
Developing faster than ever (Liferay DEVCON 2017)Developing faster than ever (Liferay DEVCON 2017)
Developing faster than ever (Liferay DEVCON 2017)Sébastien Le Marchand
 
2011-02-03 LA RubyConf Rails3 TDD Workshop
2011-02-03 LA RubyConf Rails3 TDD Workshop2011-02-03 LA RubyConf Rails3 TDD Workshop
2011-02-03 LA RubyConf Rails3 TDD WorkshopWolfram Arnold
 
SproutCore and the Future of Web Apps
SproutCore and the Future of Web AppsSproutCore and the Future of Web Apps
SproutCore and the Future of Web AppsMike Subelsky
 
Lean Php Presentation
Lean Php PresentationLean Php Presentation
Lean Php PresentationAlan Pinstein
 
Porting Rails Apps to High Availability Systems
Porting Rails Apps to High Availability SystemsPorting Rails Apps to High Availability Systems
Porting Rails Apps to High Availability SystemsMarcelo Pinheiro
 
TDC2017 | Florianopolis - Trilha DevOps How we figured out we had a SRE team ...
TDC2017 | Florianopolis - Trilha DevOps How we figured out we had a SRE team ...TDC2017 | Florianopolis - Trilha DevOps How we figured out we had a SRE team ...
TDC2017 | Florianopolis - Trilha DevOps How we figured out we had a SRE team ...tdc-globalcode
 
Harmonious Development: Via Vagrant and Puppet
Harmonious Development: Via Vagrant and PuppetHarmonious Development: Via Vagrant and Puppet
Harmonious Development: Via Vagrant and PuppetAchieve Internet
 
The "Holy Grail" of Dev/Ops
The "Holy Grail" of Dev/OpsThe "Holy Grail" of Dev/Ops
The "Holy Grail" of Dev/OpsErik Osterman
 
Multi-tenancy with Rails
Multi-tenancy with RailsMulti-tenancy with Rails
Multi-tenancy with RailsPaul Gallagher
 
Crossing the Bridge: Connecting Rails and your Front-end Framework
Crossing the Bridge: Connecting Rails and your Front-end FrameworkCrossing the Bridge: Connecting Rails and your Front-end Framework
Crossing the Bridge: Connecting Rails and your Front-end FrameworkDaniel Spector
 
RubyEnRails2007 - Dr Nic Williams - Keynote
RubyEnRails2007 - Dr Nic Williams - KeynoteRubyEnRails2007 - Dr Nic Williams - Keynote
RubyEnRails2007 - Dr Nic Williams - KeynoteDr Nic Williams
 
Rails antipattern-public
Rails antipattern-publicRails antipattern-public
Rails antipattern-publicChul Ju Hong
 
Rails antipatterns
Rails antipatternsRails antipatterns
Rails antipatternsChul Ju Hong
 
Don't RTFM, WTFM - Open Source Documentation - German Perl Workshop 2010
Don't RTFM, WTFM - Open Source Documentation - German Perl Workshop 2010Don't RTFM, WTFM - Open Source Documentation - German Perl Workshop 2010
Don't RTFM, WTFM - Open Source Documentation - German Perl Workshop 2010singingfish
 
Unit Testing for Great Justice
Unit Testing for Great JusticeUnit Testing for Great Justice
Unit Testing for Great JusticeDomenic Denicola
 
Catalyst - refactor large apps with it and have fun!
Catalyst - refactor large apps with it and have fun!Catalyst - refactor large apps with it and have fun!
Catalyst - refactor large apps with it and have fun!mold
 

Ähnlich wie Ruby For Startups (20)

Intro to Ruby on Rails
Intro to Ruby on RailsIntro to Ruby on Rails
Intro to Ruby on Rails
 
Intro to-rails-webperf
Intro to-rails-webperfIntro to-rails-webperf
Intro to-rails-webperf
 
Osiąganie mądrej architektury z Symfony2
Osiąganie mądrej architektury z Symfony2 Osiąganie mądrej architektury z Symfony2
Osiąganie mądrej architektury z Symfony2
 
Developing faster than ever (Liferay DEVCON 2017)
Developing faster than ever (Liferay DEVCON 2017)Developing faster than ever (Liferay DEVCON 2017)
Developing faster than ever (Liferay DEVCON 2017)
 
2011-02-03 LA RubyConf Rails3 TDD Workshop
2011-02-03 LA RubyConf Rails3 TDD Workshop2011-02-03 LA RubyConf Rails3 TDD Workshop
2011-02-03 LA RubyConf Rails3 TDD Workshop
 
SproutCore and the Future of Web Apps
SproutCore and the Future of Web AppsSproutCore and the Future of Web Apps
SproutCore and the Future of Web Apps
 
Lean Php Presentation
Lean Php PresentationLean Php Presentation
Lean Php Presentation
 
Porting Rails Apps to High Availability Systems
Porting Rails Apps to High Availability SystemsPorting Rails Apps to High Availability Systems
Porting Rails Apps to High Availability Systems
 
TDC2017 | Florianopolis - Trilha DevOps How we figured out we had a SRE team ...
TDC2017 | Florianopolis - Trilha DevOps How we figured out we had a SRE team ...TDC2017 | Florianopolis - Trilha DevOps How we figured out we had a SRE team ...
TDC2017 | Florianopolis - Trilha DevOps How we figured out we had a SRE team ...
 
Harmonious Development: Via Vagrant and Puppet
Harmonious Development: Via Vagrant and PuppetHarmonious Development: Via Vagrant and Puppet
Harmonious Development: Via Vagrant and Puppet
 
The "Holy Grail" of Dev/Ops
The "Holy Grail" of Dev/OpsThe "Holy Grail" of Dev/Ops
The "Holy Grail" of Dev/Ops
 
Multi-tenancy with Rails
Multi-tenancy with RailsMulti-tenancy with Rails
Multi-tenancy with Rails
 
Crossing the Bridge: Connecting Rails and your Front-end Framework
Crossing the Bridge: Connecting Rails and your Front-end FrameworkCrossing the Bridge: Connecting Rails and your Front-end Framework
Crossing the Bridge: Connecting Rails and your Front-end Framework
 
RubyEnRails2007 - Dr Nic Williams - Keynote
RubyEnRails2007 - Dr Nic Williams - KeynoteRubyEnRails2007 - Dr Nic Williams - Keynote
RubyEnRails2007 - Dr Nic Williams - Keynote
 
Rails antipattern-public
Rails antipattern-publicRails antipattern-public
Rails antipattern-public
 
Rails antipatterns
Rails antipatternsRails antipatterns
Rails antipatterns
 
Don't RTFM, WTFM - Open Source Documentation - German Perl Workshop 2010
Don't RTFM, WTFM - Open Source Documentation - German Perl Workshop 2010Don't RTFM, WTFM - Open Source Documentation - German Perl Workshop 2010
Don't RTFM, WTFM - Open Source Documentation - German Perl Workshop 2010
 
Unit Testing for Great Justice
Unit Testing for Great JusticeUnit Testing for Great Justice
Unit Testing for Great Justice
 
Webpack
Webpack Webpack
Webpack
 
Catalyst - refactor large apps with it and have fun!
Catalyst - refactor large apps with it and have fun!Catalyst - refactor large apps with it and have fun!
Catalyst - refactor large apps with it and have fun!
 

Mehr von Mike Subelsky

STAQ Development Manual (Redacted)
STAQ Development Manual (Redacted)STAQ Development Manual (Redacted)
STAQ Development Manual (Redacted)Mike Subelsky
 
Coding for uncertainty
Coding for uncertaintyCoding for uncertainty
Coding for uncertaintyMike Subelsky
 
Ruby Concurrency Realities
Ruby Concurrency RealitiesRuby Concurrency Realities
Ruby Concurrency RealitiesMike Subelsky
 
Generating Good Ideas
Generating Good IdeasGenerating Good Ideas
Generating Good IdeasMike Subelsky
 
Baltimore: A Great Place to Pick Up Ideas and Run WIth Them (Ignite)
Baltimore: A Great Place to Pick Up Ideas and Run WIth Them (Ignite)Baltimore: A Great Place to Pick Up Ideas and Run WIth Them (Ignite)
Baltimore: A Great Place to Pick Up Ideas and Run WIth Them (Ignite)Mike Subelsky
 
Let's Make Baltimore More Innovative (TEDxBaltimore)
Let's Make Baltimore More Innovative (TEDxBaltimore)Let's Make Baltimore More Innovative (TEDxBaltimore)
Let's Make Baltimore More Innovative (TEDxBaltimore)Mike Subelsky
 
Social Media for Everybody
Social Media for EverybodySocial Media for Everybody
Social Media for EverybodyMike Subelsky
 
It's Not Always Sunny in the Clouds
It's Not Always Sunny in the CloudsIt's Not Always Sunny in the Clouds
It's Not Always Sunny in the CloudsMike Subelsky
 
Introduction to SproutCore at JSConf
Introduction to SproutCore at JSConfIntroduction to SproutCore at JSConf
Introduction to SproutCore at JSConfMike Subelsky
 
Scaling Rails Applications In The Cloud
Scaling Rails Applications In The CloudScaling Rails Applications In The Cloud
Scaling Rails Applications In The CloudMike Subelsky
 
SproutCore and the Future of Web Apps
SproutCore and the Future of Web AppsSproutCore and the Future of Web Apps
SproutCore and the Future of Web AppsMike Subelsky
 

Mehr von Mike Subelsky (11)

STAQ Development Manual (Redacted)
STAQ Development Manual (Redacted)STAQ Development Manual (Redacted)
STAQ Development Manual (Redacted)
 
Coding for uncertainty
Coding for uncertaintyCoding for uncertainty
Coding for uncertainty
 
Ruby Concurrency Realities
Ruby Concurrency RealitiesRuby Concurrency Realities
Ruby Concurrency Realities
 
Generating Good Ideas
Generating Good IdeasGenerating Good Ideas
Generating Good Ideas
 
Baltimore: A Great Place to Pick Up Ideas and Run WIth Them (Ignite)
Baltimore: A Great Place to Pick Up Ideas and Run WIth Them (Ignite)Baltimore: A Great Place to Pick Up Ideas and Run WIth Them (Ignite)
Baltimore: A Great Place to Pick Up Ideas and Run WIth Them (Ignite)
 
Let's Make Baltimore More Innovative (TEDxBaltimore)
Let's Make Baltimore More Innovative (TEDxBaltimore)Let's Make Baltimore More Innovative (TEDxBaltimore)
Let's Make Baltimore More Innovative (TEDxBaltimore)
 
Social Media for Everybody
Social Media for EverybodySocial Media for Everybody
Social Media for Everybody
 
It's Not Always Sunny in the Clouds
It's Not Always Sunny in the CloudsIt's Not Always Sunny in the Clouds
It's Not Always Sunny in the Clouds
 
Introduction to SproutCore at JSConf
Introduction to SproutCore at JSConfIntroduction to SproutCore at JSConf
Introduction to SproutCore at JSConf
 
Scaling Rails Applications In The Cloud
Scaling Rails Applications In The CloudScaling Rails Applications In The Cloud
Scaling Rails Applications In The Cloud
 
SproutCore and the Future of Web Apps
SproutCore and the Future of Web AppsSproutCore and the Future of Web Apps
SproutCore and the Future of Web Apps
 

Kürzlich hochgeladen

Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Miguel Araújo
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slidespraypatel2
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking MenDelhi Call girls
 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Enterprise Knowledge
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024The Digital Insurer
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdfhans926745
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonetsnaman860154
 
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure serviceWhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure servicePooja Nehwal
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘RTylerCroy
 
Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)Allon Mureinik
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)Gabriella Davis
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptxHampshireHUG
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...Neo4j
 
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationRadu Cotescu
 
Developing An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of BrazilDeveloping An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of BrazilV3cube
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationMichael W. Hawkins
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking MenDelhi Call girls
 
Top 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live StreamsTop 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live StreamsRoshan Dwivedi
 

Kürzlich hochgeladen (20)

Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slides
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men
 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonets
 
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure serviceWhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘
 
Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
 
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organization
 
Developing An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of BrazilDeveloping An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of Brazil
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day Presentation
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
 
Top 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live StreamsTop 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live Streams
 

Ruby For Startups

  • 1. Startup Ruby Separate things that change from things that stay the same Program to an interface, not an implementation Prefer composition over inheritance Delegate, delegate, delegate You Ain't Gonna Need It (YAGNI)
  • 3. I’m Mike Subelsky @subelsky This is what I have learned while building
  • 4. What’s unique about startup programming? Facing unknown problems & unknown solutions With scarce time and resources
  • 5. “Ferocious customer-centric rapid iteration” @ericries startuplessonslearned.com
  • 6. What advice would I give myself to thrive in these conditions?
  • 7. Design thoughtfully before implementing Don’t make these specific mistakes
  • 8. Separate things that change from things that stay the same Program to interfaces, not implementations Prefer composition over inheritance Delegate, delegate, delegate You Ain't Gonna Need It (YAGNI)
  • 9. Design for Change "Separate code for general functionality from code for specialized functionality" Isolate design decisions in their own modules
  • 10. First Use of SQS SQS = RightAws::SqsGen2.new(access_key_i d, secret_access_key], { :multi_thread => true }) queue = SQS.queue("#{RAILS_ENV}_#{queue_name }")
  • 11. First Use of SQS SQS = RightAws::SqsGen2.new(access_key_i d, secret_access_key], { :multi_thread => true }) queue = SQS.queue("#{RAILS_ENV}_#{queue_name }") Then both of these changed
  • 12. Designed for Change queue = QueueFetcher.fetch(queue_name) queue.send_message({ :user_id => user_id }.to_yaml Isolates how we connect to the messaging system Isolates naming convention for the messaging system
  • 13. class QueueFetcher def self.fetch(queue_name) SQS2.queue("#{queue_env}_#{queue_name}") end private def self.queue_env APP_CONFIG['queue_context'] end end
  • 14. class QueueFetcher def self.fetch(queue_name) SQS2.queue("#{queue_env}_#{queue_name}") end private def self.queue_env APP_CONFIG['queue_context'] end end More isolation
  • 15. I don’t like this class QueueFetcher def self.fetch(queue_name) SQS2.queue("#{queue_env}_#{queue_name}") end private def self.queue_env APP_CONFIG['queue_context'] end end More isolation
  • 16. Business Logic class Envelope def deliver_first_message new_mailbox_name = Mailbox.new_mailbox_name(recipient) mailbox = Mailbox.create!(:user_id => user_id, :name => new_mailbox_name) prepare_delivery_for_spam prepare_delivery_for_forwarding(mailbox) self.first_message = true return deliver(mailbox) end
  • 17. Program to interfaces, not implementations Program to general types "Don’t call it a car if you can get away with calling it a vehicle"
  • 18. Program to interfaces, not implementations Easy for us to do with duck-typing
  • 19. class Message < ActiveRecord::Base end class ArticleMessage < Message end class SmtpMessage < Message end class SentMessage < Message end
  • 20. class Message < ActiveRecord::Base end ? class ArticleMessage < Message end class SmtpMessage < Message end class SentMessage < Message end
  • 21. Prefer composition over inheritance Equip objects with references to other objects which implement common behavior
  • 22. module S3MessageContent private def head_from_s3(filename) S3.head(APP_CONFIG['message_bucket_name'],filename) end end
  • 23. class Attachment < ActiveRecord::Base include S3MessageContent belongs_to :message before_create :put_attachment_on_s3 before_destroy :remove_from_s3 end
  • 24. Delegate, delegate, delegate Objects express certain outward behavior but actually delegate responsibility for that behavior to another object
  • 25. Delegate, delegate, delegate Really good for ActiveRecord relationships
  • 26. Delegate, delegate, delegate Really good for ActiveRecord relationships class ExternalEmailAccount < ActiveRecord::Base belongs_to :external_email_server delegate :server_info, :mail_server, :to => :external_email_server
  • 27. Delegate, delegate, delegate Forwardable and Delegate modules also make this easy
  • 28. YAGNI
  • 29. YAGNI We love solving cool, new problems with cool, new toys
  • 30. YAGNI We love solving cool, new problems with cool, new toys So sometimes we look into the future for opportunities
  • 31. YAGNI This is a fatal instinct in startups
  • 32. YAGNI I’ve built things to be super-scalable that turned out not to be core to the product
  • 33. YAGNI I’ve built things to be super-scalable that turned out not to be core to the product
  • 34. YAGNI In the early days, focus on learning not performance*
  • 35. YAGNI In the early days, focus on learning not performance* Concentrate on 80% solutions
  • 36. YAGNI In the early days, focus on learning not performance* Concentrate on 80% solutions *startuplessonslearned.com
  • 37. YAGNI Be a duct tape programmer
  • 38. YAGNI Be a duct tape programmer “...any kind of coding technique that’s even slightly complicated is going to doom your project.” http://www.joelonsoftware.com/items/ 2009/09/23.html
  • 39. Don’t make these specific mistakes
  • 40. Plan to move everything out of the web request
  • 41. Plan to move everything out of the web request ar_mailer, delayed_job, EventMachine, SQS, beanstalkd, etc.
  • 42. Plan to move everything out of the web request But remember YAGNI
  • 43. Make careful use of concurrency
  • 44. Make careful use of concurrency Prefer processes communicating via message bus (SQS, Starling, delayed_job, Rabbit MQ, etc.)
  • 45. Make careful use of concurrency Check out Unicorn http://tomayko.com/writings/unicorn-is-unix
  • 46. Make careful use of concurrency Threading: EventMachine is your friend
  • 47. Make careful use of concurrency Threading: EventMachine is your friend EMH.safe_defer do begin UserMailer.deliver_verification_email(@user, @email) rescue StandardError logger.warn("Unable to deliver signup verification to #{@user.login} due to #{$!.message}") end end
  • 48. Consider your RDBMS relationship
  • 49. Consider your RDBMS relationship Avoid touching the DB when storing non-critical data
  • 50. Consider your RDBMS relationship Avoid touching the DB when storing non-critical data Don’t use an RDBMS for things it’s not good at
  • 51. Consider your RDBMS relationship Avoid touching the DB when storing non-critical data Don’t use an RDBMS for things it’s not good at We rely heavily on AWS
  • 52. Consider your RDBMS relationship Storing large text blobs (S3) Messaging system (SQS) Logging events (SimpleDB) Caching dynamic text (S3)
  • 53. Consider your RDBMS relationship We use data_fabric gem to make master-slave transparent
  • 54. Consider your RDBMS relationship We also just used it to shard our DB
  • 55. default: &default adapter: 'mysql' username: otherinbox_mysql database: otherinbox_production shard_0: &shard_0 password: --------- <<: *controller encoding: utf8 pool: 15 shard_0_slave: &shard_0_slave <<: *controller_slave controller: &controller <<: *default <% 1.upto(10) do |n| %> host: ##### <%= "shard_#{n}: &shard_#{n}" %> <%= " <<: *default" %> controller_slave: &controller_slave <%= " host: shard#{n}.####" %> <<: *default <%= %> username: otherinbox_ro <%= "shard_#{n}_slave: &shard_#{n}_slave" %> host: #### <%= " <<: *shard_#{n}" %> <% end %> # production! production: <<: *controller <% 0.upto(10) do |n| %> <%= "shard_#{n}_production:" %> <%= " <<: *shard_#{n}" %> <% end %>
  • 56. Great DB Scaling Videos Scaling Your DB Part 1 and 2: http://railslab.newrelic.com
  • 58. DB indexes degrade over time Everyone blogs about EXPLAIN but what about ANALYZE and OPTIMIZE?
  • 59. Know Your Query Planner One of our biggest speedups came from upgrading to the latest minor MySQL version
  • 60. Organize your code nicely We have too much code in lib/*
  • 61. Organize your code nicely A lot of this stuff is plumbing and could be extracted as plugins or gems
  • 62. Organize your code nicely A lot of this stuff is plumbing and could be extracted as plugins or gems And released open source!
  • 63. Organize your code nicely ActiveMerchant has a great layout
  • 64. Organize your con g variables Dangerous / difficult to change Rarely changing Changeable at runtime
  • 65. Dangerous / difficult to change INBOX_MESSAGE = 1 ARCHIVED_MESSAGE = 2 DELETED_MESSAGE = 3 SENT_MESSAGE = 4 REJECTED_MESSAGE = 5 Nearly immutable, identical in all situations
  • 66. Rarely changing default: &default full_host_name: 'my.otherinbox.com' tech_support_address: 'support@otherinbox.com' max_subdomains_per_user: 2 max_alternate_domain_name_results: 10 domain_registration_api_timeout: 10 Dump into a YAML file
  • 67. Rarely changing development: <<: *default full_host_name: 'oib.local' domain_registration_api_timeout: 1 You want it under version control
  • 68. Changeable at Runtime >> Configuration.default_blocked_addresses => "admin,support,help,info,sales,jobs,webmaster" >> Configuration.default_blocked_addresses += ",oib" => "admin,support,help,info,sales,jobs,webmaster,oib"
  • 69. Changeable at Runtime >> Configuration.default_blocked_addresses => "admin,support,help,info,sales,jobs,webmaster" >> Configuration.default_blocked_addresses += ",oib" => "admin,support,help,info,sales,jobs,webmaster,oib" http://beautifulpixel.com/svn/plugins/settings
  • 70. Changeable at Runtime >> Configuration.default_blocked_addresses => "admin,support,help,info,sales,jobs,webmaster" >> Configuration.default_blocked_addresses += ",oib" => "admin,support,help,info,sales,jobs,webmaster,oib" http://beautifulpixel.com/svn/plugins/settings http://toolmantim.com/articles/ consolidating_your_apps_constants
  • 71. Avoid Boolean Columns Often want to know what time something changed Or you later end up needing more than 2 states
  • 72. Bundle complex view logic into Presenters
  • 73. Bundle complex view logic into Presenters class RefreshController < ApplicationController before_filter :signin_required def index render :text => JSON.generate(AdvancedRefresher.new(params).to_hash) end end
  • 74. Maybe don't test all the time, at the beginning?
  • 75. Maybe don't test all the time, at the beginning?
  • 76. Maybe don't test all the time, at the beginning? Can slow down exploratory programming
  • 77. Maybe don't test all the time, at the beginning? You’ll probably throw away half the stuff you write at the beginning anyway
  • 78. Maybe don't test all the time, at the beginning? You’ll definitely change the names of things!
  • 79. Maybe don't test all the time, at the beginning? On the other hand, tests can be a design tool (as with BDD) I wrote our SMTP code this way
  • 80. Maybe don't test all the time, at the beginning? Would be interesting to see how many Rails Rumble teams use tests
  • 83. Thank you! Slides posted at subelsky.com