restructuring rails

33
Restructuring Rails Eric Marthinsen CTO and Founder, Agile Commerce A Refactoring Story

Upload: agile-commerce

Post on 11-May-2015

453 views

Category:

Technology


1 download

TRANSCRIPT

Page 1: Restructuring rails

Restructuring Rails

Eric MarthinsenCTO and Founder, Agile Commerce

A Refactoring Story

Page 2: Restructuring rails

Overview• CTO and Founder of Agile

Commerce.

• Part of team that bought Sortfolio from 37signals in 2012.

• Have been enhancing it for about a year.

Page 3: Restructuring rails

How wastheir code?

Page 4: Restructuring rails

Rule

Stable code running a profitable company is

always successful code.

Page 5: Restructuring rails

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.

Page 6: Restructuring rails

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.

Page 7: Restructuring rails

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.

Page 8: Restructuring rails

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

Page 9: Restructuring rails

Extract Class

Page 10: Restructuring rails

Benefits• Better domain model

• Easier comprehension

•More granular tests

• Faster tests

•More hooks for mocking

•Overall, better software

Page 11: Restructuring rails

My ThesisI 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.

Page 12: Restructuring rails

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.

Page 13: Restructuring rails

“Fat model,skinny controller”

Wrong - everything should be skinny.

Page 14: Restructuring rails

Example: Search

Page 15: Restructuring rails

Step 0: Startclass 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

Page 16: Restructuring rails

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.

Page 17: Restructuring rails

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.

Page 18: Restructuring rails

Step 1class Listing < ActiveRecord::Base # associations # validationsend

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 endend

Page 19: Restructuring rails

Progress• Now expressing the collection of

Listings in the domain model.

• Creating a “preferred” interface.

• But, not DRY, and two domain concepts remain unexpressed.

Page 20: Restructuring rails

Step 2class Listing < ActiveRecord::Base # associations # validationsend

class ListingIndex def self.get_listings(is_pro, location_id, budget_id, start, offset) # lots of code endend

Page 21: Restructuring rails

Progress• ListingIndex is now DRY

• Still a couple domain concepts hidden in there.

• Adding arguments is not scalable.

Page 22: Restructuring rails

Step 3class Listing < ActiveRecord::Base # associations # validationsend

class ListingIndex def self.search(query) # less code endend

class ListingQuery def initialize(options) # set properties endend

Page 23: Restructuring rails

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.

Page 24: Restructuring rails

Step 4class ListingIndex def self.search(query) # less code endend

class ListingQuery def initialize(options) # set properties endend

class ListingResults # mostly attrsend

Page 25: Restructuring rails

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.

Page 26: Restructuring rails

Step 5: Finalclass ListingIndex def self.search(query) # less code end

def self.add_listing(listing) # trivial end

def self.remove_listing(listing) # trivial endend

class ListingQuery # no changeend

class ListingResults # mostly attrsend

Page 27: Restructuring rails

Results• Listing was split into Listing,

ListingIndex, ListingQuery, and ListingResults.

• Better design allowed for trivial introduction of ElasticSearch.

•Much easier and faster to test.

Page 28: Restructuring rails

Takeaways

Page 29: Restructuring rails

Your app probably has fewer classes

than it should.

Page 30: Restructuring rails

It’s easier to combine classes than to separate

them.

Page 31: Restructuring rails

Models don’t need to inherit from

ActiveRecord::Base

Page 32: Restructuring rails

Conventions don’t always apply.

Page 33: Restructuring rails

More InfoEric Marthinsen

[email protected]

www.agilecommerce.com/blog

@agilecommerce

@ericmarthinsen

THANKS!