Intro to DTCoreText: Moving Past UIWebView | iOS Development
Managing complexity
1. Managing Complexity
Sam Goldman
@nontrivialzeros
github.com/samwgoldman
Monday, December 17, 12
2. What is Complexity?
• Lines of code?
• Duplication?
• Coupling?
• LOC/Method?
• # Methods/Class?
• # Features?
Monday, December 17, 12
3. 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
4. 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
5. 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
6. 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
7. 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
9. OOP
• Software is factorized by encapsulation
• Controlling dependencies is key
• Conversely: Discover independencies
Monday, December 17, 12
10. Tell, Don’t Ask
• Depending on collaborators’ states breaks
encapsulation
• Depend on behaviors, not state
• Law of Demeter
Monday, December 17, 12
11. Asking
Questionnaire = 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
end
end
Monday, December 17, 12
12. Telling
Questionnaire = Struct.new(:questions) do
def render(html)
html.form do
questions.each do |question|
html.fieldset { question.render(html) }
end
end
end
end
Monday, December 17, 12
13. Telling
ShortAnswerQuestion = Struct.new(:id, :prompt) do
def render(html)
html.label(:for => id) { html.text prompt }
html.input(:type => "text", :id => id, :name => id)
end
end
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
}
end
end
Choice = Struct.new(:id, :title) do
def render(html)
html.label(:for => id) { html.text title }
html.input(:type => "radio", :id => id, :name => id)
end
end
Monday, December 17, 12
14. 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
15. 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)
end
end
Monday, December 17, 12
16. Mocks Aren’t Stubs
describe 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])
end
end
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)
end
end
Monday, December 17, 12
17. Mocks Aren’t Stubs
describe ArrearsReport do
it "displays customers who owe money" do
report = ArrearsReport.new
foo_customer = stub(:in_arrears? => true)
Stub Queries
bar_customer = stub(:in_arrears? => false)
result = report.run([foo_customer, bar_customer])
result.should eq([foo_customer])
end
end
describe Questionnaire do
it "renders every question" do
question1 = mock
question2 = mock
questionnaire = Questionnaire.new([question1, question2])
builder = stub
Mock Actions
question1.should_receive(:render).with(builder).ordered
question2.should_receive(:render).with(builder).ordered
questionnaire.render(builder)
end
end
Monday, December 17, 12
18. Values
• SmartLogic’s Nerdword project
• Services: Player, Pouch, Board
• Values: Move, Direction, Position
Monday, December 17, 12
19. Values
module Direction
HORIZONTAL = "Horizontal".freeze
VERTICAL = "Vertical".freeze
def self.opposite(direction)
if direction == HORIZONTAL
VERTICAL
else
HORIZONTAL
end
end
end
Monday, December 17, 12
20. Values
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)
end
end
Monday, December 17, 12
21. Values
Move = Struct.new(:word, :position, :direction) do
def each_position
word.length.times do |i|
yield position.shift(i, direction)
end
end
end
Monday, December 17, 12
22. 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
23. 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
24. 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
25. 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
26. Thank you
@smartlogic
facebook.com/smartlogic
github.com/smartlogic
Monday, December 17, 12