Upload
paul-coia
View
139
Download
2
Tags:
Embed Size (px)
DESCRIPTION
A presentation given in early 2011 at Ruby on Rails Oceania about a conceptual refactoring of legacy code written to handle a reality that didn't eventuate.
Citation preview
Refactoring Legacy CodeLearnings from RedBubblePaul Coia
What is Legacy Code?Untested code
Dead code
Large classes/methods
Spaghetti architecture
Models too rigid to meet new requirements
Product model
Enumeration table
Print Size,Frame Colour,Matte Colour,Frame Style
Small, Medium,Large,Red,etc.
FramedPrint
Enumeration tableEasy to add new products or options
No new columns, just insert rowsNo code changes
Can query against it“Find all artworks available on red t-shirts”
Populate web form elements easily
Domain model is in the DBProduct class is a generic type
What about domain logic?
‘Instances’ need a separate set of tables
Buying a product
Preparing the product formframed_print = ProductType.find_by_name(“FramedPrint”)
# Get the Framed Print productfp_product = artwork.products.detect do |p| p.product_type == framed_printendaovs = fp_product.available_option_values
# Group these into the optionsoptions_hash = {}aovs.each do |aov| options_hash[aov.option_value.option] << aov.option_valueend
# Order the option values, so we have S, M, L, etc.# Populate form elements
SQL view1. Load products by artwork_id2. Load available_product_options by
product_id3. Load option_values by option_value_id4. Load options by option_id
Troublesome requirementsSome products have default
optionsT-Shirt default colour and style
Options that are not constrainedCalendar start month
Inter-option constraintsLongsleeve Tee colours
Default optionsFlag on available_product_optionNeed to enforce one default per
option type
Render artwork with default options
Rendering default options
Rendering the defaults# Get the default producttee_product = artwork.get_default_product
aovs = tee_product.available_option_values
defaults = aovs.select {|aov| aov.default? }
# Group these into the options (assuming one per option)options_hash = {}defaults.each do |aov| options_hash[aov.option_value.option] << aov.option_valueend
# Need to validate that we have all required defaults# and only one per option, etc.
SQL view1. Load products by product_id2. Load available_product_options by
product_id3. Load option_values by option_value_id4. Load options by option_id
Same joins as the first example
Rendering lots of products!
Data volumes
Works Products Available Option Values0
50000000
100000000
150000000
200000000
250000000
300000000
350000000
Inter option constraintsRoundneck T-shirts can have all
colours
V-Neck can only have a few
How do you model that in the DB?Do you want to?
Original driverWant to add new products weeklyReally?
Real-world constraintsProduct preparation takes monthsCannot cover all eventualitiesNew products always required new
code anyway
Alternative approachModel product types as classesDefine options and constraints
(DSL)
Available products is a list of keys on Artwork
Available product options is a hash on the ArtworkSo is the default options
Product classclass TShirtProduct option Style, "mens", "T-Shirt" option Style, "vneck", "V-Neck T-Shirt" option Style, "longsleeve", "Long Sleeve T-Shirt" option Style, "womens", "Girly Fitted T-Shirt" option Style, "mhoodie", "Hoodie"
option Size, "xs", "XS" option Size, "small", "Small" option Size, "medium", "Medium” ...
AVAILABLE_COLORS = { "mens" => all_colors, "vneck" => [”black”, ”grey”, "navy", "white”] }
end
Database model
What have we gained?An expressive, flexible product
modelSimpler code, overallActually easier to add products nowAnd easier to modify existing ones
Nearly 25% average speed increase
What have we lost?“Find all red t-shirts”Utilise a search engine
“How many large framed prints have we sold?”Reporting via Data Warehouse
Online product addition
100s of millions of rows from the DB!