Upload
smartlogic
View
309
Download
2
Tags:
Embed Size (px)
Citation preview
Managing ComplexitySam Goldman
@nontrivialzerosgithub.com/samwgoldman
Monday, December 17, 12
What is Complexity?
• Lines of code?
• Duplication?
• Coupling?
• LOC/Method?
• # Methods/Class?
• # Features?
Monday, December 17, 12
Noticing Complexity
• Feature development costs increase
• Bugs increase superlinearly with code size
• “Boring bugs” keep happening
• New dev onboarding takes weeks
• Local changes have unexpected, non-local effects
Monday, December 17, 12
Reasoning about Complexity
• How can you compare two solutions?
• Lots of guidelines, e.g., SOLID
• Lots of cargo culting
• We need to do better than a gut check
• Patterns are still good
Monday, December 17, 12
Factorization
• Large numbers factorized by primes
• 288 = 2 × 2 × 2 × 2 × 2 × 3 × 3
• Some large numbers can’t be factorized
• 195845982777569926302400511
• Fundamental theorem of arithmetic
• No efficient algorithm
Monday, December 17, 12
Probabilistic Factors
• Take binary random variables A, B, and C
• P(A) has two possible configurations
• P(A, B) has 4
• P(A, B, C) has 8
• Joint configurations grow exponentially
Monday, December 17, 12
Probabilistic Factors
• Distributions factorized by independence
• P(A, B) = P(A)P(B) if A, B are independent
• Let A and B each have 100 discrete values
• Not independent: 10000 configurations
• Independent: 200 configurations
Monday, December 17, 12
Probabilistic Factors
Monday, December 17, 12
OOP
• Software is factorized by encapsulation
• Controlling dependencies is key
• Conversely: Discover independencies
Monday, December 17, 12
Tell, Don’t Ask
• Depending on collaborators’ states breaks encapsulation
• Depend on behaviors, not state
• Law of Demeter
Monday, December 17, 12
AskingQuestionnaire = Struct.new(:questions) do def render(html) html.form questions.each do |question| html.fieldset { case question when ShortAnswerQuestion html.label(:for => question.id) { html.text question.prompt } html.input(:type => "text", :id => question.id, :name => question.id) when MultipleChoiceQuestion html.label { html.text question.prompt } html.ul { question.choices.each do |choice| html.li { html.label(:for => choice.id) { html.text choice.title } html.input(:type => "radio", :id => choice.id, :name => choice.id) } end } end } end end endend
Monday, December 17, 12
Telling
Questionnaire = Struct.new(:questions) do def render(html) html.form do questions.each do |question| html.fieldset { question.render(html) } end end endend
Monday, December 17, 12
TellingShortAnswerQuestion = Struct.new(:id, :prompt) do def render(html) html.label(:for => id) { html.text prompt } html.input(:type => "text", :id => id, :name => id) endend
MultipleChoiceQuestion = Struct.new(:id, :prompt, :choices) do def render(html) html.label { html.text prompt } html.ul { choices.each do |choice| html.li { choice.render(html) } end } endend
Choice = Struct.new(:id, :title) do def render(html) html.label(:for => id) { html.text title } html.input(:type => "radio", :id => id, :name => id) endend
Monday, December 17, 12
Mocks
• How can we write assertions when objects hide their internal state?
• We need to assert that objects are sending the right messages to one another
Monday, December 17, 12
Mocks
describe Questionnaire do it "renders every question" do question1 = mock question2 = mock questionnaire = Questionnaire.new([question1, question2]) builder = stub
question1.should_receive(:render).with(builder).ordered question2.should_receive(:render).with(builder).ordered
questionnaire.render(builder) endend
Monday, December 17, 12
Mocks Aren’t Stubsdescribe ArrearsReport do it "displays customers who owe money" do report = ArrearsReport.new
foo_customer = stub(:in_arrears? => true) bar_customer = stub(:in_arrears? => false)
result = report.run([foo_customer, bar_customer]) result.should eq([foo_customer]) endend
describe Questionnaire do it "renders every question" do question1 = mock question2 = mock questionnaire = Questionnaire.new([question1, question2]) builder = stub
question1.should_receive(:render).with(builder).ordered question2.should_receive(:render).with(builder).ordered
questionnaire.render(builder) endend
Monday, December 17, 12
Mocks Aren’t Stubsdescribe ArrearsReport do it "displays customers who owe money" do report = ArrearsReport.new
foo_customer = stub(:in_arrears? => true) bar_customer = stub(:in_arrears? => false)
result = report.run([foo_customer, bar_customer]) result.should eq([foo_customer]) endend
describe Questionnaire do it "renders every question" do question1 = mock question2 = mock questionnaire = Questionnaire.new([question1, question2]) builder = stub
question1.should_receive(:render).with(builder).ordered question2.should_receive(:render).with(builder).ordered
questionnaire.render(builder) endend
Stub Queries
Mock Actions
Monday, December 17, 12
Values
• SmartLogic’s Nerdword project
• Services: Player, Pouch, Board
• Values: Move, Direction, Position
Monday, December 17, 12
Values
module Direction HORIZONTAL = "Horizontal".freeze VERTICAL = "Vertical".freeze
def self.opposite(direction) if direction == HORIZONTAL VERTICAL else HORIZONTAL end endend
Monday, December 17, 12
Position = Struct.new(:col, :row) do def shift(offset, direction) if direction == Direction::HORIZONTAL Position.new(col + offset, row) else Position.new(col, row + offset) end end
def previous(direction) shift(-1, direction) end
def next(direction) shift(1, direction) endend
Values
Monday, December 17, 12
Values
Move = Struct.new(:word, :position, :direction) do def each_position word.length.times do |i| yield position.shift(i, direction) end endend
Monday, December 17, 12
Values
• We happily depend on Array, Date, String...
• Create values in your domain
• Separate services from values
• Better messages, better factors
• Don’t stub values
Monday, December 17, 12
Abstractions
• RemoteFile, not S3
• PaymentProcessor, not Braintree
• Wrap services around 3rd party code
• “Ports and Adapters”
• Write integrated tests for wrapper services
• “Test double” wrapper services elsewhere
• ActiveRecord?
Monday, December 17, 12
Acceptance Tests
• “How does the client know it works?”
• Write acceptance tests your client would understand
• Write acceptance tests your client would want to read
• Write as few acceptance tests as possible
Monday, December 17, 12
Integration Tests
• “How do we know if it works?”
• Ports and Adapters is a good factorization
• Write as few integration tests as you need
• You don’t need as many as you think
Monday, December 17, 12
Thank you
@smartlogic
facebook.com/smartlogic
github.com/smartlogic
Monday, December 17, 12