5. However
•Sortfolio was a successful MVP.
•37signals’ testing philosophy differs
from my own.
•Rails 2.3.8
•Needed some work to prepare for
new features.
6. Refactoring Result
•Improved from a CodeClimate
score of 2.78 to 3.51.
•Drastically improved test coverage,
while decreasing test run time.
•Doubled, roughly, our development
velocity.
7. Today’s Objective
Share the principles I used to
improve the Sortfolio codebase in a
way that you can apply these
principles to your own projects.
8. Reduce callbacks
Embrace CSS3
Inline concerns
Add decorators
STI
Create services
Create gateways
Custom presenters
DRY
Config to ENV
ElasticSearch
Method extraction
Fewer static methods
Extract class
11. My Thesis
I believe that having classes with too
many responsibilities is the primary
cause of poor object-oriented code.
This is true of any object-oriented
platform, but doubly true of Rails.
12. Why Doubly True?
•ActiveRecord combines data and
business layers.
•View code often leaks into models
and controllers.
•Helpers are dumping grounds for
procedural code.
•Rails conventions support
overburdened objects.
15. Step 0: Start
class Listing < ActiveRecord::Base
# associations
# validations
def self.get_pro_listings(location_id, budget_id, start, offset)
# lots of code
end
def self.get_free_listings(location_id, budget_id, start, offset)
# lots of similar code
end
end
16. Object-Orientation
•An object is data and the methods
that operate on that data.
•An object should represent a single
thing.
•A class defines the type of the
object.
17. Problems
•Why does a listing know how to
find its peers?
•We’re attaching collection methods
to a type declaration.
•A domain concept is being hidden.
18. Step 1
class Listing < ActiveRecord::Base
# associations
# validations
end
class ListingIndex
def self.get_pro_listings(location_id, budget_id, start, offset)
# lots of code
end
def self.get_free_listings(location_id, budget_id, start, offset)
# lots of similar code
end
end
19. Progress
•Now expressing the collection of
Listings in the domain model.
•Creating a “preferred” interface.
•But, not DRY, and two domain
concepts remain unexpressed.
20. Step 2
class Listing < ActiveRecord::Base
# associations
# validations
end
class ListingIndex
def self.get_listings(is_pro, location_id, budget_id, start, offset)
# lots of code
end
end
21. Progress
•ListingIndex is now DRY
•Still a couple domain concepts
hidden in there.
•Adding arguments is not scalable.
22. Step 3
class Listing < ActiveRecord::Base
# associations
# validations
end
class ListingIndex
def self.search(query)
# less code
end
end
class ListingQuery
def initialize(options)
# set properties
end
end
23. Progress
•We’ve teased out another domain
concept.
•Queries are now scalable and easy
to augment.
•ListingIndex has an intuitive
#search method.
•Still one domain concept hiding in
there.
24. Step 4
class ListingIndex
def self.search(query)
# less code
end
end
class ListingQuery
def initialize(options)
# set properties
end
end
class ListingResults
# mostly attrs
end
25. Progress
•Results are now expressed in the
domain model.
•Big improvements to other areas of
the code.
•Set up for success and making big
changes behind a stable interface.
26. Step 5: Final
class ListingIndex
def self.search(query)
# less code
end
def self.add_listing(listing)
# trivial
end
def self.remove_listing(listing)
# trivial
end
end
class ListingQuery
# no change
end
class ListingResults
# mostly attrs
end
27. Results
•Listing was split into Listing,
ListingIndex, ListingQuery, and
ListingResults.
•Better design allowed for trivial
introduction of ElasticSearch.
•Much easier and faster to test.