Slide 1

Slide 1 text

Clean Code Cowboy by Piotr Solnica rubyconf.pt Portugal, Braga 2014

Slide 2

Slide 2 text

hi I’m Piotr Solnica aka solnic github.com/solnic solnic.eu @_solnic_

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

clean coders

Slide 5

Slide 5 text

– Bob Martin from The Clean Coder book “Programmers who endure and succeed amidst swirling uncertainty and nonstop pressure share a common attribute: They care deeply about the practice of creating software. They treat it as a craft. They are professionals.”

Slide 6

Slide 6 text

cowboy coders

Slide 7

Slide 7 text

http://www.techrepublic.com/blog/10-things/10-types- of-programmers-youll-encounter-in-the-field/ “The Code Cowboy is a force of nature that cannot be stopped. He or she is almost always a great programmer and can do work two or three times faster than anyone else. The problem is, at least half of that speed comes by cutting corners.”

Slide 8

Slide 8 text

Rules & Laws of Programming

Slide 9

Slide 9 text

Single Responsibility Principle Open/closed Principle Liskov Substitution Principle Interface Segregation Principle Dependency Inversion Principle

Slide 10

Slide 10 text

– DHH “I think Law of Demeter is shit”

Slide 11

Slide 11 text

– 300+ angry clean coders “IT’S THE LA W!!!1111”

Slide 12

Slide 12 text

who’s right?

Slide 13

Slide 13 text

– nobody, ever “I always wait for the green light”

Slide 14

Slide 14 text

“They’re more guidelines, than actual rules”

Slide 15

Slide 15 text

code smells

Slide 16

Slide 16 text

a code smell is a sign of a potential problem

Slide 17

Slide 17 text

following rules rigorously is a bad practice

Slide 18

Slide 18 text

the ease of refactoring is more important

Slide 19

Slide 19 text

Programming with Confidence

Slide 20

Slide 20 text

you know what you just wrote really works

Slide 21

Slide 21 text

you know what you just changed doesn’t break anything else

Slide 22

Slide 22 text

correctness verified by tests comes before concerns about implementation details

Slide 23

Slide 23 text

designing interfaces is more important than implementation details

Slide 24

Slide 24 text

confidence Great test coverage Continues Integration and Deployment Pair-programming Code review

Slide 25

Slide 25 text

Confidence == Happiness

Slide 26

Slide 26 text

Testing in Ruby == Awesome

Slide 27

Slide 27 text

My testing stack rspec bogus capybara vcr

Slide 28

Slide 28 text

describe Calculator do subject(:calculator) { Calculator.new } describe "#sum" do it "returns the sum of numbers" do expect(calculator.sum(2, 2)).to be(4) end end end

Slide 29

Slide 29 text

feature "Home page" do scenario "as a logged-in member I can buy add-ons", js: true, vcr: true do expect(page).to have_text("5 PT classes") click_on "Add" expect(page).to have_content("1,000,00 EUR") expect(page).to have_content("Buy") click_on "Buy" expect(page).to have_text("Thanks for buying an add-on") end end Use JavaScript driver Use VCR for stubbed remote calls

Slide 30

Slide 30 text

so simple!

Slide 31

Slide 31 text

so awesome!

Slide 32

Slide 32 text

Let’s write a failing test

Slide 33

Slide 33 text

Invoices App CRUD for customers CRUD for line items CRUD for invoices

Slide 34

Slide 34 text

feature "Customers" do scenario "As a user I can add a new customer and see it on the list" do visit new_customer_path find('#customer_name').set('Big Corp') find('#customer_email').set('[email protected]') find('#customer_address').set('Sunset Street 1') find('input[type=submit]').click expect(page).to have_text("Customer created") within(".customer-list") do expect(page).to have_text("Big Corp") end end end

Slide 35

Slide 35 text

class CustomersController < ApplicationController def index customers = Customer.all render locals: { customers: customers } end def new customer = Customer.new render locals: { customer: customer } end def create attributes = params.require(:customer).permit(:name, :email, :address) Customer.create(attributes) redirect_to :customers, notice: "Customer created" end end

Slide 36

Slide 36 text

Creating invoices

Slide 37

Slide 37 text

feature "Invoices" do let!(:customer) { Customer.create(name: "Big Corp") } scenario "As a user I can create a new invoice and see it on the list" do visit new_invoice_path find("#invoice_customer_id").select("Big Corp") find("#invoice_issue_date").set("2014-10-11") find("#invoice_delivery_date").set("2014-10-11") find("#invoice_due_date").set("2014-11-11") find("input[type=submit]").click expect(page).to have_text("Invoice created") within(".invoice-list") do expect(page).to have_text("DRAFT") expect(page).to have_text("Big Corp") expect(page).to have_text("2014-10-11") expect(page).to have_text("2014-11-11") end end end

Slide 38

Slide 38 text

class InvoicesController < ApplicationController def index invoices = Invoice.all render locals: { invoices: invoices } end def new invoice = Invoice.new customers = Customer.all render locals: { invoice: invoice, customers: customers } end def create attributes = params.require(:invoice).permit( :customer_id, :issue_date, :delivery_date, :due_date, :status ) Invoice.create(attributes) redirect_to :invoices, notice: "Invoice created" end end

Slide 39

Slide 39 text

Turning point!

Slide 40

Slide 40 text

feature "Invoices" do scenario "As a user I can create a new invoice and see it on the list", js: true do visit new_invoice_path find("#invoice_customer_id").select("Big Corp") find("#invoice_issue_date").set("2014-10-11") find("#invoice_delivery_date").set("2014-10-11") find("#invoice_due_date").set("2014-11-11") find("#line-item-id").select("Development") find("#new-item-qty").set("2") find(“#add-new-item”).click within(".item-list") do expect(page).to have_text("Development") expect(page).to have_text("200.00") end find("input[type=submit]").click expect(page).to have_text("Invoice created") within(".invoice-list") do expect(page).to have_text("DRAFT") expect(page).to have_text("Big Corp") expect(page).to have_text("2014-10-11") expect(page).to have_text("2014-11-11") expect(page).to have_text("200.00") end end end Select line item and provide quantity Make sure the total amount is present Things will happen on the client-side so we need JS Make sure the item was added to the list

Slide 41

Slide 41 text

$form = $("#new_invoice") $itemSelect = $("#line-item-id") $lineItems = $("#line-items") $addButton = $("#add-new-item") $itemQty = $("#new-item-qty") $addButton.on('click', (e) -> e.preventDefault() quantity = $itemQty.val() item = $itemSelect.find(':selected').data() price = parseFloat(item.price) * parseFloat(quantity) $tr = $("").append( "#{item.name}#{item.unit}#{price}" ) $form.append( $(""). attr("type", "hidden"). attr("name", "invoice[items][][line_item_id]"). val(item.id) ) $form.append( $(""). attr("type", "hidden"). attr("name", "invoice[items][][quantity]"). val(quantity) ) $lineItems.append($tr) )

Slide 42

Slide 42 text

No content

Slide 43

Slide 43 text

BUT!

Slide 44

Slide 44 text

No content

Slide 45

Slide 45 text

When it’s no longer simple to make a test pass it’s a good moment to write…more tests

Slide 46

Slide 46 text

Something must create an invoice along with its line items Something must be able to calculate the total amount based on line items

Slide 47

Slide 47 text

describe CreateInvoice do describe "#call" do it "creates an invoice with items" do customer = Customer.create(name: "Big Corp") magic = LineItem.create(name: 'Magic', unit: 'Hour', price: 2) healing = LineItem.create(name: 'Healing', unit: 'Hour', price: 4) today = Date.today params = { status: "DRAFT", customer_id: customer.id, issue_date: today, delivery_date: today, due_date: today + 1.month, items: [ { line_item_id: magic.id, quantity: 2 }, { line_item_id: healing.id, quantity: 3 }, ] } create_invoice = CreateInvoice.new(params) invoice = create_invoice.call expect(invoice.customer).to eql(customer) expect(invoice.line_items.to_a).to eql([magic, healing]) expect(invoice.total_amount).to eql(magic.price * 2 + healing.price * 3) end end end

Slide 48

Slide 48 text

class CreateInvoice attr_reader :params def initialize(params) @params = params end def call items = params[:items] invoice = Invoice.new(params.except(:items)) items.each do |item| invoice.items.build(item) end invoice.save invoice end end

Slide 49

Slide 49 text

class Invoice < ActiveRecord::Base belongs_to :customer has_many :items, class_name: "InvoiceItem" has_many :line_items, through: :items def total_amount items.map { |item| item.line_item.price * item.quantity }.sum end end BUT WAIT A MINUTE!

Slide 50

Slide 50 text

LA W OF DEMETER VIOLATION!

Slide 51

Slide 51 text

OK moving on…

Slide 52

Slide 52 text

Let’s display an invoice

Slide 53

Slide 53 text

feature "Invoices" do fixtures(:all) let(:invoice) { invoices(:first) } let(:customer) { customers(:big_corp) } scenario "as a user I can display an invoice" do visit invoices_path click_on invoice.number expect(page).to have_content(customer.name) expect(page).to have_content(invoice.number) expect(page).to have_content(invoice.issue_date) expect(page).to have_content(invoice.delivery_date) expect(page).to have_content(invoice.due_date) expect(page).to have_content(invoice.total_amount) end end missing interface!

Slide 54

Slide 54 text

class Invoice < ActiveRecord::Base belongs_to :customer has_many :items, class_name: "InvoiceItem" has_many :line_items, through: :items # TODO: implement me alias_method :number, :id def total_amount items.map { |item| item.line_item.price * item.quantity }.sum end end there we go, all good

Slide 55

Slide 55 text

oh noez we must display line items too!

Slide 56

Slide 56 text

feature "Invoices" do fixtures(:all) let(:invoice) { invoices(:first) } let(:customer) { customers(:big_corp) } scenario "as a user I can display an invoice" do visit invoices_path click_on invoice.number expect(page).to have_content(customer.name) expect(page).to have_content(invoice.number) expect(page).to have_content(invoice.issue_date) expect(page).to have_content(invoice.delivery_date) expect(page).to have_content(invoice.due_date) expect(page).to have_content(invoice.total_amount) invoice.items.each do |item| expect(page).to have_content(item.line_item.name) expect(page).to have_content(item.line_item.price * item.quantity) end end end Demeter no likey :(

Slide 57

Slide 57 text

h1 = "#{invoice.id}" table thead tr th Customer th Issue date th Delivery date th Due date th Total amount tbody tr td = invoice.customer.name td = invoice.issue_date td = invoice.delivery_date td = invoice.due_date td = number_to_currency invoice.total_amount ul - invoice.items.each do |item| li = "#{item.line_item.name} #{number_to_currency(item.line_item.price * item.quantity)}" I told you mate but you wouldn’t listen!

Slide 58

Slide 58 text

OK let’s improve this

Slide 59

Slide 59 text

describe InvoiceItem do fixtures(:invoice_items, :line_items) subject(:invoice_item) { invoice_items(:one) } describe "#price" do let(:line_item) { line_items(:magic) } it "returns the price based on quantity and line-item price" do expect(invoice_item.price).to eql(line_item.price * invoice_item.quantity) end end end

Slide 60

Slide 60 text

class InvoiceItem < ActiveRecord::Base belongs_to :invoice belongs_to :line_item def price line_item.price * quantity end end class Invoice < ActiveRecord::Base # stuff def total_amount items.to_a.sum(&:price) end end

Slide 61

Slide 61 text

the story goes on… missing Invoice#number missing validations missing print-friendly invoice page …probably more, but it’s OK

Slide 62

Slide 62 text

Optimize for confidence and productiveness

Slide 63

Slide 63 text

Work on your testing skills!

Slide 64

Slide 64 text

Focus on correctness first

Slide 65

Slide 65 text

Learn how to write tests first

Slide 66

Slide 66 text

(even when writing a code spike!)

Slide 67

Slide 67 text

No rules or laws, just guidelines

Slide 68

Slide 68 text

Always be able to refactor with confidence

Slide 69

Slide 69 text

Don’t worry be happy

Slide 70

Slide 70 text

Thank you! <3<3<3

Slide 71

Slide 71 text

Questions? (:

Slide 72

Slide 72 text

Picture credits http://lol-rofl.com/cowboy-hat-front/ http://thankheavens.com.au/2014/01/30/quick-fresh- gluten-free-ravioli-with-vine-ripe-tomato-and-toasted- garlic-sauce/ http://www.zacksplaylist.com/bobby-mcferrin-dont- worry-be-happy-review/ http://pl.wikipedia.org/wiki/Demeter