Upgrade to Pro — share decks privately, control downloads, hide ads and more …

How programming in other languages 
made my Ruby code better (RubyDay 2016)

How programming in other languages 
made my Ruby code better (RubyDay 2016)

Learning new programming languages it's like approaching new cultures: it will largely enrich your knowledge and leverage your skills. New programming languages will not only made you a better software developer in general, but they will also help you to write better Ruby code.

This talk will provide you real world examples of Ruby code evolution, using lessons learned from other languages.

Simone Carletti

November 25, 2016
Tweet

More Decks by Simone Carletti

Other Decks in Programming

Transcript

  1. Features • Simplicity and Readability • Concurrency • ComposiVon •

    Good tooling and ecosystem support • Fast builds • Excellent network libraries • High quality standard libs
  2. package dnsimple
 
 import (
 "encoding/json"
 "github.com/google/go-querystring/query"
 )
 
 const

    libraryVersion = "0.13.0"
 
 type Client struct {
 HttpClient *http.Client
 Credentials Credentials
 }
 
 func (c *Client) get(path string, obj interface{}) (*http.Response, error) {
 req, err := c.NewRequest("GET", path, nil)
 if err != nil {
 return nil, err
 }
 
 return c.Do(req, obj)
 }
 
 func (c *Client) post(path string, payload, obj interface{}) (*http.Response, error) {
 req, err := c.NewRequest("POST", path, payload)
 if err != nil {
 return nil, err
 }
 
 return c.Do(req, obj)
 }
  3. Go for the Rubyist • Excellent replacement for network operaVons

    or background tasks • APenVon to details and language expressiveness • Real concurrency • Excellent at manipulaVng JSON • Good soluVon for small web services • It will drasVcally change your percepVon of code quality
  4. func (c *Client) NewRequest(method, path string, payload interface{}) (*http.Request, error)

    {
 url := c.BaseURL + path
 
 body := new(bytes.Buffer)
 if payload != nil {
 err := json.NewEncoder(body).Encode(payload)
 if err != nil {
 return nil, err
 }
 }
 
 req, err := http.NewRequest(method, url, body)
 if err != nil {
 return nil, err
 }
 
 req.Header.Set("Content-Type", "application/json")
 req.Header.Add("Accept", "application/json")
 req.Header.Add("User-Agent", formatUserAgent(c.UserAgent))
 for key, value := range c.Credentials.Headers() {
 req.Header.Add(key, value)
 }
 
 return req, nil
 }
  5. Features • Concurrency • PaPern matching • Data Immutability •

    Excellent tools • Interoperability with Erlang • Binary manipulaVon • Iex, Mix
  6. defmodule Error do
 defexception message: "default message"
 end
 
 defmodule

    Client do
 @default_base_url "https://api.dnsimple.com"
 
 defstruct access_token: nil, base_url: @default_base_url, user_agent: nil
 
 def options do
 ~w(access_token base_url user_agent username password)a
 end
 
 def sum_odd_numbers(limit) do
 1..limit |> Enum.filter(fn(x) -> rem(x,2)== 1 end) |> Enum.sum
 end
 
 defp process_request_body(headers, nil), do: {headers, []}
 defp process_request_body(headers, body) do
 case get_header(headers, "Accept") do
 {_, "application/json"} -> {Map.put(headers, "Content-Type", @json), Poison.encode!(body)}
 _ -> {headers, body}
 end
 end
 
 defp url(%Client{base_url: base_url}, path) do
 base_url <> path
 end
  7. Elixir for the Rubyist • Syntax very close to Ruby

    • Phoenix • Iex, Mix • Community composed by a lot of ex-Ruby developers • Short learning curve (apart from funcVonal paradigm)
  8. Features • Fast • Type inference • Direct binding with

    C • Compile-Vme evaluaVon and generaVon of code
  9. class HTTP::Server
 @wants_close = false
 
 def self.new(port, handlers :

    Array(HTTP::Handler))
 new("127.0.0.1", port, handlers)
 end
 
 def self.new(port, handler)
 new("127.0.0.1", port, handler)
 end
 
 def initialize(@host : String, @port : Int32, &handler : Context ->)
 @processor = RequestProcessor.new(handler)
 end
 
 def port
 if server = @server
 server.local_address.port.to_i
 else
 @port
 end
 end
  10. require "http/server"
 
 server = HTTP::Server.new "0.0.0.0", 8080 do |context|


    context.response.headers["Content-Type"] = "text/plain"
 context.response.print("Hello world!")
 end
 
 puts "Listening on http://0.0.0.0:8080"
 server.listen
  11. Crystal for the Rubyist • Syntax very close to Ruby

    • Ruby style, but type inference • Immediate producVvity
  12. class Contact
 def refresh
 # fetches the contact from somewhere


    # and refreshes the state of the object
 end
 end
 
 c = Contact.new(id: 10)
 c.do_something
 c.refresh

  13. module Registrar
 class Domain
 def self.find(domain_name)
 resp = Registrar::Client.get("/domain/#{domain_name}")
 new(resp)


    end
 
 def self.renew(domain_name, years = 1)
 resp = Registrar::Client.post("/renew/#{domain_name}", ...)
 new(resp)
 end
 
 def initialize(resp)
 load_attributes(id: resp.id, name: resp.name)
 end
 
 def refresh
 resp = Registrar::Client.get("/domains/#{name}")
 load_attributes(id: resp.id, name: resp.name)
 end
 
 private
 
 def load_attributes(attributes) # ...
 end
 end
  14. module Registrar
 class Domain
 def self.find(domain_name)
 resp = Registrar::Client.get("/domain/#{domain_name}")
 new(resp)


    end
 
 def self.renew(domain_name, years = 1)
 resp = Registrar::Client.post("/renew/#{domain_name}", ...)
 new(resp)
 end
 
 def initialize(resp)
 load_attributes(id: resp.id, name: resp.name)
 end
 
 def refresh
 resp = Registrar::Client.get("/domains/#{name}")
 load_attributes(id: resp.id, name: resp.name)
 end
 
 private
 
 def load_attributes(attributes) # ...
 end
 end
  15. module Registrar
 class Domain
 def self.find(domain_name)
 resp = Registrar::Client.get("/domain/#{domain_name}")
 new(resp)


    end
 
 def self.renew(domain_name, years = 1)
 resp = Registrar::Client.post("/renew/#{domain_name}", ...)
 new(resp)
 end
 
 def initialize(resp)
 load_attributes(id: resp.id, name: resp.name)
 end
 
 def refresh
 resp = Registrar::Client.get("/domains/#{name}")
 load_attributes(id: resp.id, name: resp.name)
 end
 
 private
 
 def load_attributes(attributes) # ...
 end
 end
  16. module Registrar
 class Domain
 def self.find(domain_name)
 resp = Registrar::Client.get("/domain/#{domain_name}")
 new(resp)


    end
 
 def self.renew(domain_name, years = 1)
 resp = Registrar::Client.post("/renew/#{domain_name}", ...)
 new(resp)
 end
 
 def initialize(resp)
 load_attributes(id: resp.id, name: resp.name)
 end
 
 def refresh
 resp = Registrar::Client.get("/domains/#{name}")
 load_attributes(id: resp.id, name: resp.name)
 end
 
 private
 
 def load_attributes(attributes) # ...
 end
 end
  17. module Registrar
 class Domain
 def self.find(domain_name)
 resp = Registrar::Client.get("/domain/#{domain_name}")
 new(resp)


    end
 
 def initialize(resp)
 load_attributes(id: resp.id, name: resp.name)
 end
 
 def renew(period = 1)
 Registrar::Client.post("/renew/#{name}", ...)
 refresh
 end

  18. module Registrar
 class Domain
 def self.find(domain_name)
 resp = Registrar::Client.get("/domain/#{domain_name}")
 new(resp)


    end
 
 def initialize(resp)
 load_attributes(id: resp.id, name: resp.name)
 end
 
 def renew(period = 1)
 Registrar::Client.post("/renew/#{name}", ...)
 refresh
 end
 domain = Registrar::Domain.find("example.com")
 domain.renew(1)
  19. module Registrar
 class Domain
 def self.find(domain_name)
 resp = Registrar::Client.get("/domain/#{domain_name}")
 new(resp)


    end
 def initialize(resp)
 load_attributes(id: resp.id, name: resp.name)
 end
 
 def self.renew(domain_name, years = 1)
 resp = Registrar::Client.post("/renew/#{domain_name}", ...)
 new(resp)
 end
 domain = Registrar::Domain.renew("example.com")
  20. // DomainsService handles communication with the domain related
 // methods

    of the DNSimple API.
 type DomainsService struct {
 client *Client
 }
 
 // Domain represents a domain in DNSimple.
 type Domain struct {
 ID int `json:"id,omitempty"`
 AccountID int `json:"account_id,omitempty"`
 Name string `json:"name,omitempty"`
 }
 
 // DomainListOptions specifies the optional parameters you can provide
 // to customize the DomainsService.ListDomains method.
 type DomainListOptions struct {
 NameLike string `url:"name_like,omitempty"`
 RegistrantID int `url:"registrant_id,omitempty"`
 ListOptions
 }
 
 // DomainResponse represents a response from an API method that returns a Domain struct.
 type DomainResponse struct {
 Response
 Data *Domain `json:"data"`
 }
  21. module Registrar
 class Domain
 def self.find(domain_name)
 resp = Registrar::Client.get("/domains/#{domain_name}")
 new(resp)


    end
 
 def self.renew(domain_name, years = 1)
 resp = Registrar::Client.post("/renew/#{domain_name}", ...)
 new(resp)
 end
 def initialize(resp)
 load_attributes(id: resp.id, name: resp.name)
 end

  22. module Registrar
 class Domain
 def self.find(domain_name)
 resp = Registrar::Client.get("/domains/#{domain_name}")
 Structs::Domain.new(id:

    resp.id, name: resp.name)
 end
 
 def self.renew(domain_name, years = 1)
 resp = Registrar::Client.post("/renew/#{domain_name}", ...)
 Structs::Domain.new(id: resp.id, name: resp.name)
 end
 end
 
 module Structs
 class Domain
 def initialize(attributes = {})
 def assign_attributes(attributes)
 end
 end
 end
  23. My code today • Use Struct-like objects for objects that

    primarily exists for represenVng and passing around data • Do not mix heavily data-oriented objects with operaVons
  24. Ŏ Separate data from execuVon logic Ŏ No state changes,

    means very loose coupling between the data and the operaVons
  25. class Account < ActiveRecord::Base
 def score!
 # contact service for

    fraud score and recalculate fraud score
 end
 end
 
 class Order < ActiveRecord::Base
 def initialize(account)
 @account = account
 @items = []
 end
 
 def add_item(items)
 @items << items
 end
 
 def process!
 account.score!
 compute_total
 charge_account
 mark_as_completed
 notify_account
 end
 end
  26. class Account < ActiveRecord::Base
 def score!
 # contact service for

    fraud score and recalculate fraud score
 end
 end
 
 class Order < ActiveRecord::Base
 def initialize(account)
 @account = account
 @items = []
 end
 
 def add_item(items)
 @items << items
 end
 
 def process!
 account.score!
 compute_total
 charge_account
 mark_as_completed
 notify_account
 end
 end
  27. class Account < ActiveRecord::Base
 def score!
 # contact service for

    fraud score
 value = call_some_service_and_handle_errors
 # and recalculate fraud score
 update_attribute(score, value)
 end
 end
  28. class Account < ActiveRecord::Base
 def score!
 # contact service for

    fraud score
 value = call_some_service_and_handle_errors
 # and recalculate fraud score
 update_score(value)
 end
 
 def update_score(value)
 update_attribute(score, value)
 end
 end

  29. class Account < ActiveRecord::Base
 def score!
 # contact service for

    fraud score
 value = call_some_service_and_handle_errors
 # and recalculate fraud score
 update_score(value)
 score_account(account: self)
 end
 
 def update_score(value)
 update_attribute(score, value)
 end
 end
 
 class AccountService
 def initialize(*)
 end
 
 def score_account(account:)
 end
 end

  30. class Account < ActiveRecord::Base
 def score!
 score_account(account: self)
 end
 


    def update_score(value)
 update_attribute(score, value)
 end
 end
 
 class AccountService
 def initialize(*)
 end
 
 def score_account(account:)
 value = call_some_service_and_handle_errors
 account.update_score(value)
 end
 end

  31. class Account < ActiveRecord::Base
 def update_score(value) # ...
 end
 


    class AccountService
 def initialize(*) # ...
 def score_account(account:) # ...
 end
 
 class Order < ActiveRecord::Base
 def initialize(account) # ...
 def add_item(items) # ...
 def process!
 AccountService.new.score_account(account: account)
 compute_total
 charge_account
 mark_as_completed
 notify_account
 end
 end

  32. class Order < ActiveRecord::Base
 def initialize(account) # ...
 def add_item(items)

    # ...
 def process!
 AccountService.new.score_account(account: account)
 compute_total
 charge_account
 mark_as_completed
 notify_account
 end
 end

  33. class Order < ActiveRecord::Base
 def initialize(account) # ...
 def add_item(items)

    # ...
 def process!
 AccountService.new.score_account(account: account)
 compute_total
 charge_account
 mark_as_completed
 notify_account
 OrderService.new.process_order(order: self)
 end
 end
 
 class OrderService
 def initialize(*)
 end
 
 def process_order(order:)
 end
 end

  34. class Order < ActiveRecord::Base
 def initialize(account) # ...
 def add_item(items)

    # ...
 def process!
 OrderService.new.process_order(order: self)
 end
 end
 
 class OrderService
 def initialize(*)
 end
 
 def process_order(order:)
 AccountService.new.score_account(account: order.account)
 order.compute_total
 order.charge_account
 order.mark_as_completed
 order.notify_account
 end
 end

  35. class Order < ActiveRecord::Base
 def initialize(account) # ...
 def add_item(items)

    # ...
 def process! # ...
 end
 
 class OrderService
 def initialize(account_service: AccountService.new)
 @account_service = account_service
 end
 
 def process_order(order:)
 @account_service.score_account(account: order.account)
 order.compute_total
 order.charge_account
 order.mark_as_completed
 order.notify_account
 end
 end

  36. class Order < ActiveRecord::Base
 def initialize(account) # ...
 def add_item(items)

    # ...
 def process! # ...
 end
 
 class OrderService
 def initialize(account_service: AccountService.new)
 @account_service = account_service
 end
 
 def process_order(order:)
 account = order.account
 @account_service.score_account(account: account)
 order.compute_total
 order.charge_account
 order.mark_as_completed
 @account_service.notify_account(account: account)
 end
 end

  37. class Order < ActiveRecord::Base
 def initialize(account) # ...
 def add_item(items)

    # ...
 def process! # ...
 end
 
 class OrderService
 def initialize(account_service: nil, payment_service: nil)
 @account_service = account_service || AccountService.new
 @payment_service = payment_service || PaymentService.new
 end
 
 def process_order(order:)
 account = order.account
 order.processing do
 @account_service.score_account(account: account)
 finalize_order(order)
 @payment_service.charge_account(account: account)
 @account_service.notify_account(account: account)
 end
 end
 end
  38. class Account < ActiveRecord::Base
 def update_score(value) # ...
 end
 


    class Order < ActiveRecord::Base
 def initialize(account) # ...
 def add_item(items) # ...
 end

  39. class OrderService
 def initialize(account_service: nil, payment_service: nil) # ...
 def

    process_order(order:) # ...
 
 private
 
 def finalize_order(order) # ...
 end
 
 class AccountService
 def initialize(*) # ...
 def score_account(account:) # ...
 def notify_account(account:) # ...
 end
 
 class PaymentService
 def initialize(*) # ...
 def charge_account(account:, processor: nil) # ...
 end
  40. Examples in the wild @spec flatten(deep_list) :: list when deep_list:

    [any | deep_list]
 def flatten(list) do
 :lists.flatten(list)
 end
 
 @spec flatten(deep_list, [elem]) :: [elem] when elem: var, deep_list: [elem | deep_list]
 def flatten(list, tail) do
 :lists.flatten(list, tail)
 end
 
 @spec foldl([elem], acc, (elem, acc -> acc)) :: acc when elem: var, acc: var
 def foldl(list, acc, function) when is_list(list) and is_function(function) do
 :lists.foldl(function, acc, list)
 end
  41. Examples in the wild @spec flatten(deep_list) :: list when deep_list:

    [any | deep_list]
 def flatten(list) do
 :lists.flatten(list)
 end
 
 @spec flatten(deep_list, [elem]) :: [elem] when elem: var, deep_list: [elem | deep_list]
 def flatten(list, tail) do
 :lists.flatten(list, tail)
 end
 
 @spec foldl([elem], acc, (elem, acc -> acc)) :: acc when elem: var, acc: var
 def foldl(list, acc, function) when is_list(list) and is_function(function) do
 :lists.foldl(function, acc, list)
 end
  42. My code today • Use "funcVonal"-oriented service objects to code

    business logic • Model objects represent the data to be manipulated and expose a custom, well tested API to persist the data
  43. Ŏ Reduce side effects caused by states,
 improving test effecVveness

    and code maintainability Ŏ Simplify tests by reducing context setup Ŏ Slim down model objects removing third-party interacVons Ŏ Expose the business logic in a centralized place,
 with clear and easy to test methods
  44. class OrdersController
 def create
 new_order = Order.new(current_account)
 new_order.add_items(params[:items])
 
 #

    change state of new_order inside the OrderService
 Registry(:order_service).process_order(order: new_order)
 end
 end

  45. describe "GET create" do
 it "handles a successful request" do


    expect(Registry(:order_service)).to receive(:process_order).
 with(...).
 and_return(...)
 
 post :create, account_id: @_account.to_param, ...
 end
 end

  46. describe "GET create" do
 it "handles a successful request" do


    expect(Registry(:order_service)).to receive(:process_order).
 with(...).
 and_return(...) # ?!?
 
 post :create, account_id: @_account.to_param, ...
 
 # how can I set controller or view expectations # that depends on processing applied to new_order from the service?
 end
 end

  47. describe "GET create" do
 let(:created_order) { Order.new(id: 10, items: ...,

    ...) }
 
 it "handles a successful request" do
 expect(Registry(:order_service)).to receive(:process_order).
 with(...).
 and_return(created_order)
 
 post :create, account_id: @_account.to_param, ...
 
 # how can I set controller or view expectations # that depends on processing applied to new_order from the service?
 end
 end

  48. describe "GET create" do
 let(:created_order) { Order.new(id: 10, items: ...,

    ...) }
 
 it "handles a successful request" do
 expect(Registry(:order_service)).to receive(:process_order).
 with(...).
 and_return(created_order)
 
 post :create, account_id: @_account.to_param, ...
 
 expect(response).to redirect_to(
 order_url(@_account, order_id: created_order.id)
 )
 end
 end

  49. Ŏ Simplify tests by being able to bePer isolate dependencies

    Ŏ Improved unit tesVng
 (use integraVon tests for comprehensive coverage) Ŏ Speed up tests by not having to test unnecessary code due to internal state changes
  50. class CoffeeMaker
 def initializer(company)
 @company = company
 end
 
 def

    prepare_espresso
 begin
 machine.prepare(@company, :espresso)
 rescue CoffeeMachine::CoffeeIsFinished
 machine.grind_coffee_beans(@company)
 retry
 end
 end
 
 def balance
 machine.balance(@company)
 end
 
 private
 
 def machine
 @machine ||= CoffeeMachine.connect("api.dnsimple.coffee")
 end
 end
  51. class CoffeeMaker
 def initializer(company)
 @company = company
 end
 
 def

    prepare_espresso
 begin
 machine.prepare(@company, :espresso)
 rescue CoffeeMachine::CoffeeIsFinished
 machine.grind_coffee_beans(@company)
 retry
 end
 end
 
 def balance
 machine.balance(@company)
 end
 
 private
 
 def machine
 @machine ||= CoffeeMachine.connect("api.dnsimple.coffee")
 end
 end
  52. RSpec.describe CoffeeMaker do
 subject { described_class.new("company") }
 
 describe "#prepare_espresso"

    do
 it "creates and returns an espresso" do
 expect_any_instance_of(CoffeeMachine)
 .to receive(:prepare)
 .with("company", :espresso)
 .and_return(returned = Object.new)
 
 expect(subject.prepare_espresso).to be(returned)
 end
  53. RSpec.describe CoffeeMaker do
 subject { described_class.new("company") }
 let(:machine) { CoffeeMachine.new

    }
 
 describe "#prepare_espresso" do
 it "creates and returns an espresso" do
 expect(subject).to receive(:machine).and_return(machine)
 
 expect(machine)
 .to receive(:prepare)
 .with("company", :espresso)
 .and_return(returned = Object.new)
 
 expect(subject.prepare_espresso).to be(returned)
 end
  54. describe "#prepare_espresso" do
 it "re-fills the machine when coffee is

    finished" do
 expect(subject).to receive(:machine).at_least(1).and_return(machine)
 
 # first time fail, missing coffee
 expect(machine)
 .to receive(:prepare)
 .with("company", :espresso)
 .and_raise(CoffeeMachine::CoffeeIsFinished)
 expect(machine)
 .to receive(:grind_coffee_beans)
 
 # now we have enough coffee
 expect(machine)
 .to receive(:prepare)
 .with("company", :espresso)
 .and_return(returned = Object.new)
 
 expect(subject.prepare_espresso).to be(returned)
 end
 end
  55. class CoffeeMaker
 def initialize(company)
 @company = company
 end
 
 def

    prepare_espresso
 machine.prepare(@company, :espresso)
 rescue CoffeeMachine::CoffeeIsFinished
 machine.grind_coffee_beans(@company)
 retry
 end
 
 def balance
 machine.balance(@company)
 end
 
 private
 
 def machine
 @machine ||= CoffeeMachine.connect("api.dnsimple.coffee")
 end
 end
  56. class CoffeeMaker
 def initialize(company)
 @company = company
 end
 
 def

    prepare_espresso
 machine.prepare(@company, :espresso)
 rescue CoffeeMachine::CoffeeIsFinished
 machine.grind_coffee_beans(@company)
 retry
 end
 
 def balance
 machine.balance(@company)
 end
 
 private
 
 def machine
 @machine ||= CoffeeMachine.connect("api.dnsimple.coffee")
 end
 end
  57. class CoffeeMaker
 def initialize(company, machine: CoffeeMachine.connect("api.dnsimple.coffee"))
 @company = company
 @machine

    = machine
 end
 
 def prepare_espresso
 machine.prepare(@company, :espresso)
 rescue CoffeeMachine::CoffeeIsFinished
 machine.grind_coffee_beans(@company)
 retry
 end
 
 def balance
 machine.balance(@company)
 end
 
 private
 
 def machine
 @machine
 end
 end
  58. RSpec.describe CoffeeMaker do
 subject { described_class.new("company") }
 let(:machine) { CoffeeMachine.new

    }
 
 describe "#prepare_espresso" do
 it "creates and returns an espresso" do
 expect(subject).to receive(:machine).and_return(machine)
 
 expect(machine)
 .to receive(:prepare)
 .with("company", :espresso)
 .and_return(returned = Object.new)
 
 expect(subject.prepare_espresso).to be(returned)
 end

  59. RSpec.describe CoffeeMaker do
 subject { described_class.new("company") }
 let(:machine) { CoffeeMachine.new

    }
 
 describe "#prepare_espresso" do
 it "creates and returns an espresso" do
 expect(subject).to receive(:machine).and_return(machine)
 
 expect(machine)
 .to receive(:prepare)
 .with("company", :espresso)
 .and_return(returned = Object.new)
 
 expect(subject.prepare_espresso).to be(returned)
 end

  60. RSpec.describe CoffeeMaker do
 subject { described_class.new("company", machine: machine) }
 let(:machine)

    { TestCoffeeMachine.new }
 
 describe "#execute_something" do
 it "creates and returns something else" do
 expect(subject.execute_something).to eq("else")
 end
 end
 end
 class TestCoffeeMachine
 def prepare(*)
 end
 def grind_coffee_beans(*)
 end
 end

  61. class CoffeeMaker
 def initialize(company, machine: CoffeeMachine.connect("api.dnsimple.coffee"))
 @company = company
 @machine

    = machine
 end
 
 def prepare_espresso # ...
 def balance # ...
 
 private
 
 def machine # ...
 end
 
 CoffeeMaker.new("company")
  62. class CoffeeMaker
 def initialize(company, machine:)
 @company = company
 @machine =

    machine
 end
 
 def prepare_espresso # ...
 def balance # ...
 
 private
 
 def machine # ...
 end
 
 CoffeeMaker.new("company", machine: CoffeeMachine.connect("api.dnsimple.coffee"))
  63. class CoffeeMaker
 def initialize(company, machine: Registry.coffee_machine)
 @company = company
 @machine

    = machine
 end
 
 def prepare_espresso # ...
 def balance # ...
 
 private
 
 def machine # ...
 end
 
 CoffeeMaker.new("company") # CoffeeMachine.connect("api.dnsimple.coffee")) CoffeeMaker.new("company", machine: CoffeeMachine.connect("api.dnsimple.test"))
  64. Examples in the wild func Print(a ...interface{}) (n int, err

    error) {
 return Fprint(os.Stdout, a...)
 }
 
 func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
 p := newPrinter()
 p.doPrint(a, false, false)
 n, err = w.Write(p.buf)
 p.free()
 return
 }
  65. Examples in the wild func Print(a ...interface{}) (n int, err

    error) {
 return Fprint(os.Stdout, a...)
 } func Printf(format string, a ...interface{}) (n int, err error) {
 return Fprintf(os.Stdout, format, a...)
 }
 
 func Println(a ...interface{}) (n int, err error) {
 return Fprintln(os.Stdout, a...)
 }
  66. My code today • Inject database connecVons such as Redis

    • Inject service dependencies into a service object • Inject environment-based adapters or strategies • Inject no-op test adapters to replace external services
  67. Ŏ Improve decoupling between resources Ŏ Simplify tests by reducing

    unnecessary setup or code Ŏ Increase test effecVveness by reducing mock objects
  68. def authorize(...)
 begin
 le = Letsencrypt.new("sandbox")
 le.authorize(...)
 rescue Letsencrypt::Error =>

    error
 # do something with error
 end
 end LetsencryptController.issue CertificateLetsencryptNewissueRequestCommand.execute
 CertificateService.issue_certificate
 CertificateService.authorize
 ...
  69. class Letsencrypt
 
 # Base error for Letsencrypt
 class Error

    < StandardError
 end
 
 class DomainValidationError < Error
 end
 def initialize(environment:, adapter: DatabaseAdapter.new)
 @environment = environment.to_str
 ENVIRONMENTS.include?(@environment) or
 raise ArgumentError, "Invalid Letsencrypt env `#{@environment}`"
 # ...
 end
  70. class Letsencrypt
 
 # Base error for Letsencrypt
 class Error

    < StandardError
 end
 
 class DomainValidationError < Error
 end
 def initialize(environment:, adapter: DatabaseAdapter.new)
 @environment = environment.to_str
 ENVIRONMENTS.include?(@environment) or
 raise ArgumentError, "Invalid Letsencrypt env `#{@environment}`"
 # ...
 end
  71. class Letsencrypt
 
 # Base error for Letsencrypt
 class Error

    < StandardError
 end
 
 class DomainValidationError < Error
 end

  72. class Letsencrypt
 
 # Error tag for Letsencrypt.
 module Error


    end
 
 # Base error for Letsencrypt.
 class LetsencryptError < StandardError
 include Error
 end
 
 class DomainValidationError < LetsencryptError
 end
  73. class Letsencrypt
 
 # Error tag for Letsencrypt.
 module Error


    end
 
 # Base error for Letsencrypt.
 class LetsencryptError < StandardError
 include Error
 end
 
 class DomainValidationError < LetsencryptError
 end 
 # Raised when the arguments are wrong.
 class ArgumentError < ::ArgumentError
 include Error
 end
  74. class Letsencrypt
 
 def initialize(environment:, adapter: DatabaseAdapter.new)
 @environment = environment.to_str


    ENVIRONMENTS.include?(@environment) or
 raise ArgumentError, " ... `#{@environment}`"
 # ...
 end
  75. class Letsencrypt
 
 def initialize(environment:, adapter: DatabaseAdapter.new)
 @environment = environment.to_str


    ENVIRONMENTS.include?(@environment) or
 raise Letsencrypt::ArgumentError, " ... `#{@environment}`"
 # ...
 end
  76. def authorize(...)
 begin
 le = Letsencrypt.new("sandbox")
 le.authorize(...)
 rescue Letsencrypt::LetsencryptError =>

    error
 # do something with base error
 rescue Letsencrypt::Error => error
 # do something with any error
 end
 end
  77. class CustomLibrary
 class Error < StandardError
 end
 
 def execute


    this_will_fail
 rescue Timeout::Error
 raise Error, "a timeout occurred"
 end
 
 def this_will_fail
 raise Timeout::Error, "something took long"
 end
 end
  78. class CustomLibrary
 class Error < StandardError
 end
 
 def execute


    this_will_fail
 rescue Timeout::Error
 raise Error, "a timeout occurred"
 end
 
 def this_will_fail
 raise Timeout::Error, "something took long"
 end
 end
  79. begin
 lib = CustomLibrary.new
 lib.execute
 rescue => error
 puts "Error:"


    puts " #{error.class}: #{error.message}"
 cause = error.cause
 puts "Caused by:"
 puts " #{cause.class}: #{cause.message}"
 end

  80. begin
 lib = CustomLibrary.new
 lib.execute
 rescue => error
 puts "Error:"


    puts " #{error.class}: #{error.message}"
 cause = error.cause
 puts "Caused by:"
 puts " #{cause.class}: #{cause.message}"
 end
 
 Error:
 CustomLibrary::Error: a timeout occurred
 Caused by:
 Timeout::Error: something took long

  81. begin
 lib = CustomLibrary.new
 lib.execute
 rescue => error
 puts "Error:"


    puts " #{error.class}: #{error.message}"
 cause = error.cause
 puts "Caused by:"
 puts " #{cause.class}: #{cause.message}"
 end
 
 Error:
 CustomLibrary::Error: a timeout occurred
 Caused by:
 Timeout::Error: something took long

  82. class CustomLibrary
 class Error < StandardError
 end
 
 def execute


    this_will_fail
 rescue Timeout::Error
 raise Error, "a timeout occurred"
 end
 
 def this_will_fail
 raise Timeout::Error, "something took long"
 end
 end
  83. My code today • Provide an Error module to tag

    excepVon from a specific package • Provide a default package excepVon, that is called PackageError and include Error • Subclass PackageError when needed
  84. Ŏ Facilitate error handling by wrapping all the excepVons a

    library can raise into the library namespace Ŏ Repackage excepVons, sVll giving access to the original error and without overriding backtrace
  85. def execute(task, category = "default", action = "index")
 puts "Task

    #{task}: #{category}/#{action}"
 end
 
 execute("task 1")
 execute("task 2", "cat2")
 execute("task 3", "cat3", "create")
 execute("task 11", default?, "create")
  86. func ListDomains(account string, options *DomainListOptions) (*DomainsResponse, error) {
 //
 //


    }
 
 type DomainListOptions struct {
 NameLike string `url:"name_like,omitempty"`
 RegistrantID int `url:"registrant_id,omitempty"`
 
 ListOptions
 }

  87. func ListDomains(account string, options *DomainListOptions) (*DomainsResponse, error) {
 //
 //


    }
 
 type DomainListOptions struct {
 NameLike string `url:"name_like,omitempty"`
 RegistrantID int `url:"registrant_id,omitempty"`
 
 ListOptions
 }
 
 type ListOptions struct {
 Page int `url:"page,omitempty"`
 PerPage int `url:"per_page,omitempty"`
 Sort string `url:"sort,omitempty"`
 }
  88. client.Domains.ListDomains("1010", nil)
 
 listOptions := &DomainListOptions{ListOptions{Page: 2, PerPage: 20}}
 client.Domains.ListDomains("1010",

    listOptions)
 
 listOptions := &DomainListOptions{}
 listOptions.Page = 2
 listOptions.PerPage = 20
 client.Domains.ListDomains("1010", listOptions)

  89. Simplicity is complicated Preferable to have just one way, or

    at least fewer, simpler ways. Features add complexity. We want simplicity. Features hurt readability. We want readability. Readability is paramount. Rob Pike dotGo 2015 hPps:/ /www.youtube.com/watch?v=rFejpH_tAHM
  90. Limited use of alias def update_registry_name_servers(name_servers)
 # ...
 end 


    alias update_registry_nameservers update_registry_name_servers
 alias registry_name_servers= update_registry_name_servers
 
 def refresh_name_server_status
 # ...
 end 
 alias refresh_nameserver_status refresh_name_server_status
  91. Single, Simple, Clear API module Dnsimple
 module Services
 
 #

    DomainRegistrarService is the service responsible for the Domain operations
 # that involves registration, transfer or registrar-related actions.
 #
 # Note that these methods don't simply change data at the registrar. Occasionally,
 # they can also change data or states in the domain object, and return it.
 class DomainRegistrarService
 
 # Unlocks the domain at the registry.
 #
 # @param [Domain] domain
 # @return [Domain]
 # @raise [Registrar::Error]
 def unlock_domain(domain:)
 
 # Performs a check of the domain at the registry.
 #
 # @see Registrar::Domain.check_domain
 # @param domain [Domain]
 # @return [Registrar::Structs::Availability] containing the result of the check
 # @raise [Registrar::Error]
 def check_domain(domain:)
  92. Simplicity vs Verbosity class DomainCheckCommand
 include Command::Command
 
 def execute(domain_name)


    domain = Domain.new(name: domain_name.to_str)
 # ...
 check = DomainRegistrarService.new.check_domain(domain: domain)
 # ...
 end
 end
 
 class DomainRegistrarService
 def check_domain(domain:)
 Registrar::Domain.check_domain(domain.name)
 end
 end