managing complexity

26
Managing Complexity Sam Goldman @nontrivialzeros github.com/samwgoldman Monday, December 17, 12

Upload: smartlogic

Post on 18-Jun-2015

307 views

Category:

Documents


2 download

TRANSCRIPT

Page 1: Managing complexity

Managing ComplexitySam Goldman

@nontrivialzerosgithub.com/samwgoldman

Monday, December 17, 12

Page 2: Managing complexity

What is Complexity?

• Lines of code?

• Duplication?

• Coupling?

• LOC/Method?

• # Methods/Class?

• # Features?

Monday, December 17, 12

Page 3: Managing complexity

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

Page 4: Managing complexity

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

Page 5: Managing complexity

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

Page 6: Managing complexity

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

Page 7: Managing complexity

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

Page 8: Managing complexity

Probabilistic Factors

Monday, December 17, 12

Page 9: Managing complexity

OOP

• Software is factorized by encapsulation

• Controlling dependencies is key

• Conversely: Discover independencies

Monday, December 17, 12

Page 10: Managing complexity

Tell, Don’t Ask

• Depending on collaborators’ states breaks encapsulation

• Depend on behaviors, not state

• Law of Demeter

Monday, December 17, 12

Page 11: Managing complexity

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

Page 12: Managing complexity

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

Page 13: Managing complexity

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

Page 14: Managing complexity

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

Page 15: Managing complexity

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

Page 16: Managing complexity

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

Page 17: Managing complexity

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

Page 18: Managing complexity

Values

• SmartLogic’s Nerdword project

• Services: Player, Pouch, Board

• Values: Move, Direction, Position

Monday, December 17, 12

Page 19: Managing complexity

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

Page 20: Managing complexity

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

Page 21: Managing complexity

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

Page 22: Managing complexity

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

Page 23: Managing complexity

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

Page 24: Managing complexity

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

Page 25: Managing complexity

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

Page 26: Managing complexity

Thank you

@smartlogic

facebook.com/smartlogic

github.com/smartlogic

Monday, December 17, 12