Slide 1

Slide 1 text

Less code, more confidence Lucas "Tonchis" Tolchinsky

Slide 2

Slide 2 text

Less code, more confidence Lucas "Tonchis" Tolchinsky

Slide 3

Slide 3 text

I hate debugging

Slide 4

Slide 4 text

feeling lost in code

Slide 5

Slide 5 text

legacy code

Slide 6

Slide 6 text

nobody knows how it works

Slide 7

Slide 7 text

less code

Slide 8

Slide 8 text

libraries over frameworks

Slide 9

Slide 9 text

what makes a library simple?

Slide 10

Slide 10 text

where is the less code?

Slide 11

Slide 11 text

Agenda • Readability • Explicit is better than implicit • Avoiding redundancy

Slide 12

Slide 12 text

disclaimer!

Slide 13

Slide 13 text

people != code

Slide 14

Slide 14 text

Readability

Slide 15

Slide 15 text

code has two purposes

Slide 16

Slide 16 text

tell computers what to do

Slide 17

Slide 17 text

explain to humans how it's done

Slide 18

Slide 18 text

assert-url 1 def test_foo 2 post "/foo" 3 4 assert_equal 201, last_response.status 5 assert_equal "http://localhost:8080/foo/1", 6 last_response.location 7 end

Slide 19

Slide 19 text

assert-url 1 include AssertUrl 2 3 def test_foo 4 post "/foo" 5 6 assert_equal 201, last_response.status 7 assert_path_equal "/foo/1", last_response.location 8 end

Slide 20

Slide 20 text

assert-url 1 module AssertUrl 2 PARTS = %W[scheme host port path query fragment] 3 4 PARTS.each do |part| 5 error = const_set("#{part.capitalize}Error", Class.new(StandardError)) 6 7 define_method(:"assert_#{part}_equal") do |expected, value| 8 expected = normalize(part, expected) 9 value = urify(value).send(part.to_sym) 10 11 expected == value || (raise error, "expected #{expected}, got #{value}") 12 end 13 end 53 end

Slide 21

Slide 21 text

why write it this way?

Slide 22

Slide 22 text

optimized for? who is this code

Slide 23

Slide 23 text

assert-url 1 module AssertUrl 2 PARTS = %W[scheme host port path query fragment] 3 4 PARTS.each do |part| 5 error = const_set("#{part.capitalize}Error", Class.new(StandardError)) 6 7 define_method(:"assert_#{part}_equal") do |expected, value| 8 expected = normalize(part, expected) 9 value = urify(value).send(part.to_sym) 10 11 expected == value || (raise error, "expected #{expected}, got #{value}") 12 end 13 end 53 end

Slide 24

Slide 24 text

assert-url 1 module AssertUrl 2 PARTS = %W[scheme host port path query fragment].each do |part| 3 const_set("#{part.capitalize}Error", Class.new(StandardError)) 4 end 5 6 def assert_scheme_equal(expected, value) 7 value = urify(value).scheme 8 9 expected.to_s == value || raises(SchemeError, expected, value) 10 end 11 12 def assert_host_equal(expected, value) 13 value = urify(value).host 14 15 expected == value || raises(HostError, expected, value) 16 end 137 end

Slide 25

Slide 25 text

why is this better?

Slide 26

Slide 26 text

reading > writing

Slide 27

Slide 27 text

easier to load into your head

Slide 28

Slide 28 text

less legacy code

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

Explicit is better than implicit

Slide 31

Slide 31 text

what we choose to show

Slide 32

Slide 32 text

what we choose to hide

Slide 33

Slide 33 text

abstraction

Slide 34

Slide 34 text

Devise 1 require "devise" 2 3 # In the model 4 class User 5 devise :database_authenticatable 6 end 7 8 # In the controller 9 class ApplicationController < ActionController::Base 10 before_action :authenticate_user! 11 end

Slide 35

Slide 35 text

this is practical, but...

Slide 36

Slide 36 text

dangerous abstraction

Slide 37

Slide 37 text

Devise 1 require "devise" 2 3 # In the model 4 class User 5 devise :database_authenticatable 6 end 7 8 # In the controller 9 class ApplicationController < ActionController::Base 10 before_action :authenticate_user! 11 end

Slide 38

Slide 38 text

Devise 112 def self.define_helpers(mapping) #:nodoc: 113 mapping = mapping.name 114 115 class_eval <<-METHODS, __FILE__, __LINE__ + 1 116 def authenticate_#{mapping}!(opts={}) 117 opts[:scope] = :#{mapping} 118 warden.authenticate!(opts) if !devise_controller? || opts.delete(:force) 119 end 120 METHODS 121 end https://github.com/plataformatec/devise/blob/c9a2d0654e9fc1aaebe6f99ef6fbc55c55a91fdd/lib/devise/controllers/helpers.rb#L112 112 def self.define_helpers(mapping) #:nodoc: 113 mapping = mapping.name 114 115 class_eval <<-METHODS, __FILE__, __LINE__ + 1 116 def authenticate_#{mapping}!(opts={}) 117 opts[:scope] = :#{mapping} 118 warden.authenticate!(opts) if !devise_controller? || opts.delete(:force) 119 end 120 METHODS 121 end

Slide 39

Slide 39 text

learning in the worst moment possible

Slide 40

Slide 40 text

Shield 1 # In the model 2 class User 3 include Shield::Model 4 5 def self.fetch(username) 6 # Find user 7 end 8 end 9 10 # In the controller/routes 11 @user = User.authenticate("tonchis", "pass1234")

Slide 41

Slide 41 text

Shield https://github.com/cyx/shield/blob/40e573a23f07aeb6d88adc2c36967ca67c5aa018/lib/shield.rb#L78-L84 78 def authenticate(username, password) 79 user = fetch(username) 80 81 if user and is_valid_password?(user, password) 82 return user 83 end 84 end 78 def authenticate(username, password) 79 user = fetch(username) 80 81 if user and is_valid_password?(user, password) 82 return user 83 end 84 end

Slide 42

Slide 42 text

the strategy is explicit

Slide 43

Slide 43 text

black magic!

Slide 44

Slide 44 text

less legacy code

Slide 45

Slide 45 text

No content

Slide 46

Slide 46 text

> La perfection est atteinte, non pas lorsqu'il n'y a plus rien à ajouter, mais lorsqu'il n'y a plus rien à retirer. - Antoine de Saint-Exupéry

Slide 47

Slide 47 text

Avoiding redundancy

Slide 48

Slide 48 text

flexibility is a double-edged sword

Slide 49

Slide 49 text

harder to write homogenous code

Slide 50

Slide 50 text

homogenous code is predictable

Slide 51

Slide 51 text

predictable code is more readable

Slide 52

Slide 52 text

homogenous code is an old quest

Slide 53

Slide 53 text

Fat 1 require "fat" 2 3 hash = { 4 "foo" => { 5 "bar" => { 6 "baz" => "value", 7 }, 8 }, 9 } 10 11 hash["foo"]["not"]["baz"] # undefined method [] for nil 12 13 Fat.at(hash, "foo", "not", "baz") # Fat::FatError: "foo.not" is nil 14 15 Fat.at(hash, "foo.bar.baz") # When keys are Strings. 16 17 Fat.at(hash, "foo:bar:baz") # When keys are Symbols. 1 require "fat" 2 3 hash = { 4 "foo" => { 5 "bar" => { 6 "baz" => "value", 7 }, 8 }, 9 } 10 11 hash["foo"]["not"]["baz"] # undefined method [] for nil 12 13 Fat.at(hash, "foo", "not", "baz") # Fat::FatError: "foo.not" is nil 14 15 Fat.at(hash, "foo.bar.baz") # When keys are Strings. 16 17 Fat.at(hash, "foo:bar:baz") # When keys are Symbols.

Slide 54

Slide 54 text

Fat https://gitlab.com/tonchis/fat/blob/v1.0.0/ext/fat/fat.c#L63 63 static inline void parse_fields(VALUE args, VALUE *fields) { 64 if (RARRAY_LEN(args) == 1) { 65 *fields = rb_str_split(RARRAY_PTR(args)[0], "."); 66 67 if (RARRAY_LEN(*fields) == 1) { 68 VALUE split = rb_str_split(RARRAY_PTR(args)[0], ":"); 69 70 if (RARRAY_LEN(split) == 1) { 71 rb_raise(rb_eFatError, "Single argument expected..."); 72 } else { 73 for (long i = 0; i < RARRAY_LEN(split); i++) { 74 rb_ary_store(*fields, i, str_to_sym(RARRAY_AREF(split, i))); 75 } 76 } 77 } 78 } else { 79 *fields = args; 80 } 81 }

Slide 55

Slide 55 text

has nothing to do with Hash navigation

Slide 56

Slide 56 text

Is it worth it? More work for the reader to understand.

Slide 57

Slide 57 text

Is it worth it? More room for bugs or attack vectors.

Slide 58

Slide 58 text

can we live without the feature?

Slide 59

Slide 59 text

how can we find redundancy?

Slide 60

Slide 60 text

are all my users using 100% of the code?

Slide 61

Slide 61 text

code coverage

Slide 62

Slide 62 text

interface coverage

Slide 63

Slide 63 text

Cuba 1 require "cuba" 2 3 # This block catches `GET /home` 4 Cuba.define do 5 on "home" do 6 on get do 7 res.write("I'm home!") 8 end 9 end 10 end

Slide 64

Slide 64 text

Cuba 1 require "cuba" 2 3 # Which is the same as this one 4 Cuba.define do 5 on get do 6 on "home" do 7 res.write("I'm home!") 8 end 9 end 10 end

Slide 65

Slide 65 text

Cuba 1 require "cuba" 2 3 # And the same as this other one 4 Cuba.define do 5 on get, "home" do 6 res.write("I'm home!") 7 end 8 end

Slide 66

Slide 66 text

Cuba https://github.com/soveran/cuba/blob/1dce54d0926c5d6e5daab033a8e9685c6faa4227/lib/cuba.rb#L167 167 def on(*args, &block) 168 try do 169 @captures = [] 170 171 return unless args.all? { |arg| match(arg) } 172 173 yield(*captures) 174 175 if res.status.nil? 176 if res.body.empty? 177 res.status = 404 178 else 179 res.headers[CONTENT_TYPE] ||= DEFAULT_CONTENT_TYPE 180 res.status = 200 181 end 182 end 183 184 halt(res.finish) 185 end 186 end 167 def on(*args, &block) 168 try do 169 @captures = [] 170 171 return unless args.all? { |arg| match(arg) } 172 173 yield(*captures) 174 175 if res.status.nil? 176 if res.body.empty? 177 res.status = 404 178 else 179 res.headers[CONTENT_TYPE] ||= DEFAULT_CONTENT_TYPE 180 res.status = 200 181 end 182 end 183 184 halt(res.finish) 185 end 186 end

Slide 67

Slide 67 text

is this so bad?

Slide 68

Slide 68 text

code conventions!

Slide 69

Slide 69 text

Syro 1 require "syro" 2 3 # This catches `GET /home` 4 Syro.new do 5 on "home" do 6 get do 7 res.write("I'm home!") 8 end 9 end 10 end

Slide 70

Slide 70 text

Syro 1 require "syro" 2 3 # `GET /home` => 404 Not Found 4 Syro.new do 5 get do 6 on "home" do 7 res.write("I'm home!") 8 end 9 end 10 end

Slide 71

Slide 71 text

Syro https://github.com/soveran/syro/blob/1.0.0/lib/syro.rb#L176-L182 176 def get 177 if root? && req.get? 178 yield 179 180 halt(res.finish) 181 end 182 end 176 def get 177 if root? && req.get? 178 yield 179 180 halt(res.finish) 181 end 182 end

Slide 72

Slide 72 text

more predictable code

Slide 73

Slide 73 text

less legacy code

Slide 74

Slide 74 text

No content

Slide 75

Slide 75 text

less code • Optimize for readability • Make your solutions explicit • Avoid redundant interfaces

Slide 76

Slide 76 text

thank you!