$30 off During Our Annual Pro Sale. View Details »

Clean Code Cowboy

Clean Code Cowboy

Have you ever had a fear of writing code? That maybe what you’re about to write makes no sense. Or maybe it’s not following best practices. Maybe it’s full of code smells? Or even worse maybe it doesn’t do what it’s supposed to do? You wrote something and you think “is this right and does it look OK!?”. Do you feel confident about your code? Are you happy with it? Programming is fun but it can be simply stressful - let’s just chill out, write a failing test and make it pass!

Piotr Solnica

October 14, 2014
Tweet

More Decks by Piotr Solnica

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

  3. View Slide

  4. clean coders

    View Slide

  5. – 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.”

    View Slide

  6. cowboy coders

    View Slide

  7. 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.”

    View Slide

  8. Rules & Laws
    of
    Programming

    View Slide

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

    View Slide

  10. – DHH
    “I think Law of Demeter is shit”

    View Slide

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

    View Slide

  12. who’s right?

    View Slide

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

    View Slide

  14. “They’re more guidelines, than
    actual rules”

    View Slide

  15. code smells

    View Slide

  16. a code smell is a sign
    of a potential problem

    View Slide

  17. following rules rigorously
    is a bad practice

    View Slide

  18. the ease of refactoring
    is more important

    View Slide

  19. Programming
    with
    Confidence

    View Slide

  20. you know what you
    just wrote really works

    View Slide

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

    View Slide

  22. correctness verified by tests
    comes before concerns about
    implementation details

    View Slide

  23. designing interfaces is
    more important than
    implementation details

    View Slide

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

    View Slide

  25. Confidence
    ==
    Happiness

    View Slide

  26. Testing in Ruby
    ==
    Awesome

    View Slide

  27. My testing stack
    rspec
    bogus
    capybara
    vcr

    View Slide

  28. 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

    View Slide

  29. 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

    View Slide

  30. so simple!

    View Slide

  31. so awesome!

    View Slide

  32. Let’s write a failing
    test

    View Slide

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

    View Slide

  34. 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

    View Slide

  35. 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

    View Slide

  36. Creating invoices

    View Slide

  37. 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

    View Slide

  38. 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

    View Slide

  39. Turning point!

    View Slide

  40. 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

    View Slide

  41. $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)
    )

    View Slide

  42. View Slide

  43. BUT!

    View Slide

  44. View Slide

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

    View Slide

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

    View Slide

  47. 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

    View Slide

  48. 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

    View Slide

  49. 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!

    View Slide

  50. LA
    W OF DEMETER VIOLATION!

    View Slide

  51. OK moving on…

    View Slide

  52. Let’s display an
    invoice

    View Slide

  53. 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!

    View Slide

  54. 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

    View Slide

  55. oh noez we must
    display line items too!

    View Slide

  56. 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 :(

    View Slide

  57. 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!

    View Slide

  58. OK let’s improve this

    View Slide

  59. 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

    View Slide

  60. 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

    View Slide

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

    View Slide

  62. Optimize for
    confidence and
    productiveness

    View Slide

  63. Work on your testing
    skills!

    View Slide

  64. Focus on correctness
    first

    View Slide

  65. Learn how to write
    tests first

    View Slide

  66. (even when writing a
    code spike!)

    View Slide

  67. No rules or laws,
    just guidelines

    View Slide

  68. Always be able to
    refactor with confidence

    View Slide

  69. Don’t worry
    be happy

    View Slide

  70. Thank you!
    <3<3<3

    View Slide

  71. Questions? (:

    View Slide

  72. 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

    View Slide