Slide 1

Slide 1 text

SOLID Principles Through Tests

Slide 2

Slide 2 text

@sebasoga Sebastián Sogamoso

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

Kung-fu

Slide 6

Slide 6 text

Shaolin Kungfu

Slide 7

Slide 7 text

The most well known principle is called Six Harmonies

Slide 8

Slide 8 text

Three External Harmonies

Slide 9

Slide 9 text

Three Internal Harmonies

Slide 10

Slide 10 text

We all follow some sort of principle

Slide 11

Slide 11 text

SOLID Principles Through Tests

Slide 12

Slide 12 text

Set of 5 object design level principles

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

S O L I D

Slide 15

Slide 15 text

Prevent our systems from rotting

Slide 16

Slide 16 text

Code that will save us time and money

Slide 17

Slide 17 text

Cats follow the 6 harmonies to practice Kungfu We should follow principles when writing code RECAP

Slide 18

Slide 18 text

Story Time

Slide 19

Slide 19 text

Some facts of this story are true and some are made up

Slide 20

Slide 20 text

We’ll call him Steven Seagull

Slide 21

Slide 21 text

E-commerce system • Initial status: supports a few shipping methods • Requirement: painlessly add new shipping methods as needed

Slide 22

Slide 22 text

class Order ... def ship case @store.shipping_method when Shipping::RegularMethod @store.shipping_method.pack(@items, @buyer, @seller).ship when Shipping::OvernightMethod origin = @seller.address destination = “#{@buyer.address_line1}, #{@buyer.zip}" @store.shipping_method.ship_order(origin, destination, @items) when Shipping::SameDayMethod addresses = [@seller.address, @buyer.complete_address] @store.shipping_method.pack(@items, addresses).send end end ... end

Slide 23

Slide 23 text

def ship case @store.shipping_method when Shipping::RegularMethod @store.shipping_method.pack(@items, @buyer, @seller).ship when Shipping::OvernightMethod origin = @seller.address destination = “#{@buyer.address_line1}, #{@buyer.zip}" @store.shipping_method.ship_order(origin, destination, @items) when Shipping::SameDayMethod addresses = [@seller.address, @buyer.complete_address] @store.shipping_method.pack(@items, addresses).send when Shipping::NewMethod ... end end

Slide 24

Slide 24 text

Shotgun surgery

Slide 25

Slide 25 text

class Order ... def ship(shipping_method) case shipping_method when Shipping::RegularMethod shipping_method.pack(@items, @buyer, @seller).ship when Shipping::OvernightMethod origin = @seller.address destination = “#{@buyer.address_line1}, #{@buyer.zip}" shipping_method.ship_order(origin, destination, @items) when Shipping::SameDayMethod addresses = [@seller.address, @buyer.complete_address] @store.shipping_method.pack(@items, addresses).send else shipping_method.ship_order(self) end end ... end

Slide 26

Slide 26 text

We were able to extend a class behavior, without modifying it RECAP

Slide 27

Slide 27 text

S O L I D Open Closed

Slide 28

Slide 28 text

class Order ... def ship(shipping_method) case shipping_method when Shipping::RegularMethod shipping_method.ship_order(self) when Shipping::OvernightMethod shipping_method.ship_order(self) when Shipping::SameDayMethod shipping_method.ship_order(self) else shipping_method.ship_order(self) end end ... end

Slide 29

Slide 29 text

.................................. .................................. ....................... Finished in 129600.024296s 91 examples, 21 failures, 0 skips

Slide 30

Slide 30 text

describe Order do describe "#ship" do ... subject { Order.new } context "when shipping method is a Shipping::RegularMethod" do it "uses Shipping::RegularMethod to ship the order" do allow(shipping_method).to receive(class: Shipping::RegularMethod) subject.ship expect(shipping_method). to have_received_message_chain(:pack, :ship). with(items, seller, buyer) end end context "when shipping method is a Shipping::OvernightMethod" do it "uses Shipping::OvernightMethod to ship the order" do allow(shipping_method).to receive(class: Shipping::OvernightMethod) subject.ship expect(shipping_method). to have_received(:ship_order) end end context "when shipping method is a Shipping::SameDayMethod" do it "uses Shipping::SameDayMethod to ship the order" do allow(shipping_method).to receive(class: Shipping::SameDayMethod) subject.ship expect(shipping_method). to have_received_message_chain(:pack, :send). end end end end

Slide 31

Slide 31 text

context "when shipping method is a Shipping::OvernightMethod" do it "uses Shipping::OvernightMethod to ship the order" do allow(shipping_method).to receive(class: Shipping::OvernightMethod) subject.ship expect(shipping_method). to have_received(:ship_order) end end

Slide 32

Slide 32 text

Listen to your tests

Slide 33

Slide 33 text

Tests are the first client of the system

Slide 34

Slide 34 text

“TDD doesn’t drive good design. TDD gives you immediate feedback about what’s likely to be bad design” Kent Beck

Slide 35

Slide 35 text

Order Shipping method

Slide 36

Slide 36 text

Concrete things change a lot, abstract things change much less frequently

Slide 37

Slide 37 text

class Order ... def ship(shipping_method) case shipping_method when Shipping::RegularMethod shipping_method.pack(@items, @buyer, @seller).ship when Shipping::OvernightMethod origin = @seller.address destination = “#{@buyer.address_line1}, #{@buyer.zip}" shipping_method.ship_order(origin, destination, @items) when Shipping::SameDayMethod addresses = [@seller.address, @buyer.complete_address] @store.shipping_method.pack(@items, addresses).send else shipping_method.ship_order(self) end end ... end

Slide 38

Slide 38 text

module Shipping class RegularMethod < BaseMethod ... def self.ship_order(order) new. pack(order.items, order.origin, order.destination) .ship end ... end end

Slide 39

Slide 39 text

class Order ... def ship(shipping_method) case shipping_method when Shipping::RegularMethod shipping_method.ship_order(self) when Shipping::OvernightMethod origin = @seller.address destination = “#{@buyer.address_line1}, #{@buyer.zip}" shipping_method.ship_order(origin, destination, @items) when Shipping::SameDayMethod addresses = [@seller.address, @buyer.complete_address] @store.shipping_method.pack(@items, addresses).send else shipping_method.ship_order(self) end end ... end

Slide 40

Slide 40 text

class Order ... def ship(shipping_method) case shipping_method when Shipping::OvernightMethod origin = @seller.address destination = “#{@buyer.address_line1}, #{@buyer.zip}" shipping_method.ship_order(origin, destination, @items) when Shipping::SameDayMethod addresses = [@seller.address, @buyer.complete_address] @store.shipping_method.pack(@items, addresses).send else shipping_method.ship_order(self) end end ... end

Slide 41

Slide 41 text

describe Order do describe "#ship" do ... subject { Order.new } context "when shipping method is a Shipping::OvernightMethod" do it “ships the order using the given shipping method" do order = Order.new allow(shipping_method).to receive(class: Shipping::OvernightMethod) allow(shipping_method).to receive(:ship_order) { ‘ordered shipped’ } expect(order.ship(shipping_method).to eq ‘ordered shipped’ end end context "when shipping method is a Shipping::SameDayMethod" do it “ships the order using the given shipping method" do order = Order.new allow(shipping_method).to receive(class: Shipping::SameDayMethod) allow(shipping_method).to receive(:ship_order) { ‘ordered shipped’ } expect(order.ship(shipping_method).to eq ‘ordered shipped’ end end context "when shipping method is a different type" do it "uses Shipping::SameDayMethod to ship the order" do allow(shipping_method).to receive(class: nil) subject.ship expect(shipping_method). to have_received_message_chain(:pack, :send). end end end end

Slide 42

Slide 42 text

describe Order do ... describe "#ship" do let(:shipping_method) { double(:shipping_method) } subject { Order.new } it “ships the order using the given shipping method" do order = Order.new.pack allow(shipping_method).to receive(:ship_order) { ‘ordered shipped’ } expect(order.ship(shipping_method).to eq ‘ordered shipped’ end ... end

Slide 43

Slide 43 text

class Order ... def ship(shipping_method) case shipping_method.class when Shipping::RegularMethod shipping_method.ship_order(self) when Shipping::OvernightMethod shipping_method.ship_order(self) when Shipping::SameDayMethod shipping_method.ship_order(self) end end ... end

Slide 44

Slide 44 text

class Order ... def ship(shipping_method) shipping_method.ship_order(self) end ... end

Slide 45

Slide 45 text

Started to depend on an abstractions, not on concrete implementations RECAP

Slide 46

Slide 46 text

S O L I D Dependency Inversion

Slide 47

Slide 47 text

module Shipping class WikedCrazyMethod < BaseMethod ... def fold(items, origin, destination) ... end def fly ... end ... end end

Slide 48

Slide 48 text

Contract tests

Slide 49

Slide 49 text

shared_examples "a shipping method" do it do should respond_to(:ship_order). with(1).argument end it “can be used to ship an order” do order = Order.new shipping_method = subject expect(order).to_not be_shipped order.ship(shipping_method) expect(order).to be_shipped end end

Slide 50

Slide 50 text

describe Shipping::StandardMethod do subject { Shipping::WikedCrazyMethod } it_should_behave_like "a shipping method” ... end

Slide 51

Slide 51 text

.................................. .................................. ....................... Finished in 129600.024296s 91 examples, 1 failure, 0 skips

Slide 52

Slide 52 text

module Shipping class WikedCrazyMethod < BaseMethod def self.ship_order(order) items = order.items * Math::PI origin = order.seller.name.reverse new.fold(items, origin, order.buyer).fly end ... end end

Slide 53

Slide 53 text

.................................. .................................. ....................... Finished in 129600.024296s 91 examples, 0 failures, 0 skips

Slide 54

Slide 54 text

All clases conform with the same contract RECAP

Slide 55

Slide 55 text

S O L I D Liskov Substitution

Slide 56

Slide 56 text

Subclasses should be substitutable for their base classes

Slide 57

Slide 57 text

Shipping cost calculation Formula that considers: • The amount of items (except for digital items). • A value that represents the distance between the origin and the destination . • A value that represents a fixed rate. items_quantity * distance * fixed_rate

Slide 58

Slide 58 text

• Initial status: a change on any formula items, requires changing the class • Requirement: if only one formula item is changed the rest of the items shouldn’t be affected Shipping cost calculation

Slide 59

Slide 59 text

describe Shipping::Cost do describe "#calculate" do ... subject { Shipping::StandardMethod.new(order, origin, destination) } context "when all items in the order are digital" do ... end context "when exactly one item in the order is not digital" do context "when the origin and destination are in the same state" do ... end context "when the origin and destination are in the same country" do ... end context "when the origin and destination are in different countries" do ... end end context "when more than one item in the order are not digital" do context "when the origin and destination are in the same state" do ... end context "when the origin and destination are in the same country" do ... end context "when the origin and destination are in different countries" do ... end end context "when origin city doesn't ship to the destination" do ... end end ... end

Slide 60

Slide 60 text

module Shipping class Cost def initialize(order, origin, destination) ... end def calculate items_quantity * rate * distance end ... private def items_quantity @order.items.reject(:digital?).lenght end def rate if items_quantity > 1 9.99 else 5.99 end end def distance return 0 if @origin.does_not_ship_to? @destination return 1 if @origin.same_state? @destination return 2 if @origin.same_country? @destination 3 end end end

Slide 61

Slide 61 text

Burroteca

Slide 62

Slide 62 text

No content

Slide 63

Slide 63 text

Donkey + Discotheque

Slide 64

Slide 64 text

No content

Slide 65

Slide 65 text

describe Shipping::Cost do ... describe "#calculate" do subject { Shipping::StandardMethod.new(order, origin, destination) } context "when all items in the order are digital" do ... end context "when exactly one item in the order is not digital" do context "when the origin and destination are in the same state" do ... end context "when the origin and destination are in the same country" do ... end context "when the origin and destination are in different countries" do ... end end context "when more than one item in the order are not digital" do context "when the origin and destination are in the same state" do ... end context "when the origin and destination are in the same country" do ... end context "when the origin and destination are in different countries" do ... end end context "when the origin city doesn't ship to the destination" do ... end end ... end

Slide 66

Slide 66 text

describe Shipping::Cost do ... describe "#calculate" do subject { Shipping::StandardMethod.new(order, origin, destination) } context "when all items in the order are digital" do ... end context "when exactly one item in the order is not digital" do context "when the origin and destination are in the same state" do ... end context "when the origin and destination are in the same country" do ... end context "when the origin and destination are in different countries" do ... end end context "when more than one item in the order are not digital" do context "when the origin and destination are in the same state" do ... end context "when the origin and destination are in the same country" do ... end context "when the origin and destination are in different countries" do ... end end context "when the origin city doesn't ship to the destination" do ... end end ... end

Slide 67

Slide 67 text

• "when all items in the order are digital” • "when exactly one item in the order is not digital" • "when more than one item in the order are not digital"

Slide 68

Slide 68 text

describe Shipping::Items do describe "#quantity" do context "when all items in the order are digital" do ... end context "when exactly one item in the order is not digital" do ... end context "when more than one item in the order are not digital" do ... end end end

Slide 69

Slide 69 text

describe Shipping::Cost do ... describe "#calculate" do subject { Shipping::StandardMethod.new(order, origin, destination) } context "when all items in the order are digital" do ... end context "when exactly one item in the order is not digital" do context "when the origin and destination are in the same state" do ... end context "when the origin and destination are in the same country" do ... end context "when the origin and destination are in different countries" do ... end end context "when more than one item in the order are not digital" do context "when the origin and destination are in the same state" do ... end context "when the origin and destination are in the same country" do ... end context "when the origin and destination are in different countries" do ... end end context "when the origin city doesn't ship to the destination" do ... end end ... end

Slide 70

Slide 70 text

• "when the origin and destination are in the same state” • "when the origin and destination are in the same country” • "when the origin and destination are in different countries” • "when the origin city doesn't ship to the destination"

Slide 71

Slide 71 text

describe Shipping::Distance do describe "#calculate" do context "when the origin and destination are in the same state" do ... end context "when the origin and destination are in the same country" do ... end context "when the origin and destination are in different countries" do ... end context "when the origin city doesn't ship to the destination" do ... end end end

Slide 72

Slide 72 text

module Shipping class Cost def initialize(order, origin, destination) ... end def calculate items_quantity * rate * distance end private def items_quantity Shipping::Items.new(@order).quantity end def rate if items_quantity > 1 9.99 else 5.99 end end def distance Shipping::Distance.new(origin, destination).calculate end end end

Slide 73

Slide 73 text

.................................. .................................. ....................... Finished in 129600.024296s 91 examples, 0 failures, 0 skips

Slide 74

Slide 74 text

module Shipping class Cost def initialize(order, origin, destination) ... end def calculate items_quantity * rate * distance end private def items_quantity Shipping::Items.new(@order).quantity end def rate Shipping::Rate.new(items_quantity).calculate end def distance Shipping::Distance.new(origin, destination).calculate end end end

Slide 75

Slide 75 text

A class should have one, and only one, reason to change RECAP

Slide 76

Slide 76 text

S O L I D Single Responsibility

Slide 77

Slide 77 text

Steven Seagull was very happy and so were we

Slide 78

Slide 78 text

S O L I D Interface Segregation

Slide 79

Slide 79 text

Make fine-grained interfaces that are client-specific

Slide 80

Slide 80 text

Why should you care?

Slide 81

Slide 81 text

Respond to changing requirements

Slide 82

Slide 82 text

Manage dependencies

Slide 83

Slide 83 text

“I’m not a great programmer; I’m just a good programmer with great habits” Kent Beck

Slide 84

Slide 84 text

The SOLID principles will help you acquire great object design habits

Slide 85

Slide 85 text

No content

Slide 86

Slide 86 text

No content

Slide 87

Slide 87 text

Use your best judgement

Slide 88

Slide 88 text

No content

Slide 89

Slide 89 text

We all follow some sort of principle

Slide 90

Slide 90 text

Choose yours wisely

Slide 91

Slide 91 text

Obrigado @sebasoga

Slide 92

Slide 92 text

Credits • Me at RubyConf Uruguay: https://secure.flickr.com/photos/94260666@N05/14251620082/ • James: https://www.flickr.com/photos/125635394@N08/14894541332/in/photolist-oGbqdL-9neeBd-9neeD3-o8YsWp-osB7nF-oqTkmd-osef7e-9nfoAb-oDxDMY- o9mymh-ooPmHN-o9mAa9-o9mwaf-o9mBVn-o9mBf4-o9nDYM-o9mEds-o9nEaZ-o9mD9L-oqPJSm-ooPvhG-o9nCTk-o9nKtV-ooPnL9-oqz7uD-ooPvxw-o9mAu6- oqz4o8-o9mCpZ-ooPzR1-oqPGfY-oqD4hS-oqz4TM-osB7Kz-ooPv5s-osBfmB-o9mzY5-osBdax-oqz8ar-o9mJj4-o9mH3f-o9mAoy-oqReWV-o9mwVU-o9mEAJ-oqz2Tz- o9mv9N-o9mBia-o9nESR-oqD2xE • Golden Gate Bridge: https://www.flickr.com/photos/salim/402620871/in/photolist-gxzCVN-3gHR9W-5Wkky-7L2Rdu-7uys1-5R11B1- e7CfhP-6DZhWn-38B3gv-4tN2Xq-kAVAG-dUSUQy-6zLofj-Bzxaa-4jmZiD-Bxufq-bfCFgT-5V84Y-n3nkmB-Bzxus-Bzwuu-bVnpiR-n8Vxzv-axiND-ekFJyQ-h881MJ- bdn9iB-dX8ZeH-ba8H-dZcoQ1-5eikhM-cdFJjh-BzwPa-iW215d-5cTsTq-aPkLxV-6u7c4x-eD5M4-hFTq1W-78nwcX-sXd8e-fCMz9o-813icF-39Wstq-ev17J3-nPu9UG- c6bssC-6EDRSs-dQjTmN-axiXV • Gatto Mimmo: https://www.flickr.com/photos/gattomimmo/2125052913/in/photolist-3eqRoo-bRHqDi-akDPPi-9znES-482wnh-57PniE-7k88aj-2eEDEV-4eMsck- NEZ6A-eAR9nm-6c4a3f-c6p92h-2Y3Xp-78ATbn-9cdK3N-48ERpQ-6bANod-guvqG4-NEZ7f-DV4uM-47Xq5i-95D52m-7GfyjG-3E8qR-2vnfir-6qkkAk-d6XrpW-Ey2hB- NEZ6L-8mNVog-fEgakj-7oGZng-7YpL32-6ScqrQ-qPBk1-4Pvgs-93vS3Y-h1Gipq-47XHfB-PsFae-7YsZMh-8mPMr2-h1G7cr-gux5xc-2xcrmY-owS2Tg-8Jt8ro- ddKAb-332pLa • Shaolin Kungfu 1: https://www.flickr.com/photos/larique/3235902917/in/ photolist-5VWRcB-5KpggW-5KpioE-5VWDwg-5Kk2Qp-5Gf7j-4Jntb-4Jnio-4JoKG-4JoeM-4MZ6M-4JnKm-4JoTZ-4Johi-4JoEA-3xWQA- gsor-7t8exA-5VWPb4-5W24MC-gvWkgi-yKgw6-5W226u-5W1U5w-oUZif-8Mo9FE-8Mk5bg-8Modbf-5Kk3Wa-gwdV3T-fAcBQo-xTuHS-fFtqEg-5W1M8y-5W1KgG- oUZii-oV14w-crzuKN-9Hpikx-9HpdF6-9Hsgeh-9Hsa5Q-9HpiUD-9HsaBC-9HpkEg-9HskyJ-9Hs5jC-9Hpgiv-9HpvH2-9Hpd9k • Shaolin Kungfu 2: https://www.flickr.com/photos/larique/3236693046/in/photolist-5W1U5w-oUZif-8Mo9FE-8Mk5bg-8Modbf-5Kk3Wa-gwdV3T-fAcBQo-xTuHS- fFtqEg-5W1M8y-5W1KgG-oUZii-oV14w- crzuKN-9Hpikx-9HpdF6-9Hsgeh-9Hsa5Q-9HpiUD-9HsaBC-9HpkEg-9HskyJ-9Hs5jC-9Hpgiv-9HpvH2-9Hpd9k-9HsjxW-9Hse2b-9HpucM-9Hs7rj-9Hsigj-9Hpnki-9Hpfn K-9HpbAX-9HppSF-9Hs8xd-9HsiEQ-9HscSS-9Hscgw-9HpuJc-9Hps8M-9Hptd4-9Hprfp-9Hso4o-9HpmM8-9HpoSp-9HppnX-9Hs6X9-9Hs3Lm

Slide 93

Slide 93 text

• Shaolin Kungfu 3: https://www.flickr.com/photos/larique/3235896099/in/photolist-5VWPb4-5W24MC-gvWkgi-yKgw6-5W226u-5W1U5w- oUZif-8Mo9FE-8Mk5bg-8Modbf-5Kk3Wa-gwdV3T-fAcBQo-xTuHS-fFtqEg-5W1M8y-5W1KgG-oUZii-oV14w- crzuKN-9Hpikx-9HpdF6-9Hsgeh-9Hsa5Q-9HpiUD-9HsaBC-9HpkEg-9HskyJ-9Hs5jC-9Hpgiv-9HpvH2-9Hpd9k-9HsjxW-9Hse2b-9HpucM-9Hs7rj-9Hsigj-9Hpnki-9Hpfn K-9HpbAX-9HppSF-9Hs8xd-9HsiEQ-9HscSS-9Hscgw-9HpuJc-9Hps8M-9Hptd4-9Hprfp-9Hso4o • Uncle Bob: https://www.flickr.com/photos/moofbong/4206618799/in/ photolist-7pJ2dk-7pMCyY-7pMWo5-7pHWfe-7pMJ7s-7pMSoJ-7pJ16V-7pHSMt-7pHXpr-7pMNVw-7pHL6Z-7pHHPt-7pMGow-7pHMqv-seGMM-seGNW-7j2wCX- seHCx-seHbn-seHx4-seHmT-seHod-seGDJ-seHFq-seHDM-seGEB-seGL1-seHK6-seGHD-seGYT-seHBi-seGFC-seHHY-seH4e-seHiT-seGJp-seGK7-seHsD-seHkn- seHH7-seHxv-seH5f-seH6s-seGGF-seH3p-seHy8-seH9M-seHzx-b1hoEp-Gj1Wx/ • Kent Beck: https://www.flickr.com/photos/skrb/238102518/in/photolist-Hfu8-n3kzQ-n3kWh-n3kkG-7Jnjvu-6foWR2-6foWRa-djMXPd-cWkWKS-oF4y3- oCY4q-7Tx3yN-3p6hoi-4SrHSK-3Uu3Z3-8QyWVd-3paQF1-3p6hcT-3o4296-3o423c-3p62PD-3p62FM-3p62ze-3p6aQz-3p6h26-3paA55-3payDY-3p5VdK-3p5TaV-3p5S x6-3p5Wnc-3p5YdM-3pavp9-3p5Y4B-3pawg1-3pauEh-3pay1L-3patuL-3p5UiB-3p61zD-3paqDq-3parJE-3p5XKn-3paujq-3pavM9-3p5ZFR-3p5SEk-3pavdN-3p5YMZ-3 pauxq • Superheroes: https://www.flickr.com/photos/charlot17/4648822207 • Beware: https://www.flickr.com/photos/atoach/3922401551/in/photolist-AmPFE-2xwi8T-64GFek-6kWVRg-9vAqpD-jiG1mG-4tj1U-cvyg1f-4eHMx9-65XzLZ- czjMeb-6Y6JC8-5Hja23-6YBkjX-7tRTC3-64VB6C-mmd6zP-9LaWU3-7T61c5-kDDFs3-gmYDTg-ekvzx5-9mrc-7JphLV-6x1xw1-atzvpP-5Nz7gs-5y9TPm-nxj3e1- hHNuw9-bpnnzS-7B43Lf-9BMD7M-bvZDbg-jwss4F-bUhfe1-8ype7F-51LBV-n9rWsV-KugGj-dCPvvp-cECRaC-7GjgEU-jMaCY-h66jGg-jGjbF-9jF4P-diSx2r-nsVjAr- ceUeoG • Burroteca: http://www.jackmag.com.co/articles/burroteca/ Credits