Slide 1

Slide 1 text

Maintaining a 6yo Project Simone Carle, / / DNSimple

Slide 2

Slide 2 text

@underwaterruby

Slide 3

Slide 3 text

Simone Carle4 @weppos

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

Cycle of Maintenance

Slide 6

Slide 6 text

day 1

Slide 7

Slide 7 text

year 1

Slide 8

Slide 8 text

year 2

Slide 9

Slide 9 text

year 3

Slide 10

Slide 10 text

Game ver!

Slide 11

Slide 11 text

the DNSimple app

Slide 12

Slide 12 text

How big is it? It's hard to find reasonable code metrics

Slide 13

Slide 13 text

Lines of code are useful only when bragging to project managers.

Slide 14

Slide 14 text

➜ dnsimple git:(master) cloc . 4366 text files. 4357 unique files. 1174 files ignored. https://github.com/AlDanial/cloc v 1.66 T=22.99 s (139.3 files/s, 8110.2 lines/s) -------------------------------------------------------------------------------- Language files blank comment code -------------------------------------------------------------------------------- Ruby 2484 26061 5974 93289 JavaScript 18 3151 2460 15161 ERB 479 2187 0 14462 SASS 46 618 153 4253 SQL 4 2903 2685 4253 CoffeeScript 86 938 190 3043 XML 17 1 0 1918 JSON 46 32 0 1088 YAML 11 122 260 509 CSS 3 11 0 309 HTML 7 48 0 263 Bourne Again Shell 1 13 0 63 -------------------------------------------------------------------------------- SUM: 3202 36085 11722 138611 --------------------------------------------------------------------------------

Slide 15

Slide 15 text

+----------------------+-------+-------+---------+---------+-----+-------+ | Name | Lines | LOC | Classes | Methods | M/C | LOC/M | +----------------------+-------+-------+---------+---------+-----+-------+ | Controllers | 6481 | 4833 | 123 | 738 | 6 | 4 | | Helpers | 1825 | 1216 | 10 | 202 | 20 | 4 | | Models | 9540 | 6138 | 147 | 966 | 6 | 4 | | Mailers | 390 | 274 | 11 | 41 | 3 | 4 | | Javascripts | 2419 | 1850 | 36 | 490 | 13 | 1 | | Libraries | 14452 | 9877 | 340 | 1465 | 4 | 4 | | Api specs | 3519 | 2634 | 0 | 3 | 0 | 876 | | Clock specs | 19 | 14 | 0 | 0 | 0 | 0 | | Command specs | 8084 | 6066 | 3 | 4 | 1 | 1514 | | Controller specs | 10261 | 7710 | 0 | 1 | 0 | 7708 | | Feature specs | 143 | 109 | 0 | 3 | 0 | 34 | | Finder specs | 3873 | 2918 | 0 | 11 | 0 | 263 | | Helper specs | 1943 | 1502 | 2 | 1 | 0 | 1500 | | Integration specs | 32 | 25 | 0 | 0 | 0 | 0 | | Lib specs | 13906 | 10769 | 2 | 35 | 17 | 305 | | Mailer specs | 720 | 560 | 0 | 0 | 0 | 0 | | Model specs | 14719 | 11441 | 0 | 18 | 0 | 633 | | Processor specs | 1354 | 1074 | 0 | 0 | 0 | 0 | | Request specs | 5101 | 3843 | 0 | 6 | 0 | 638 | | Routing specs | 6088 | 4890 | 0 | 0 | 0 | 0 | | Serializer specs | 576 | 460 | 0 | 0 | 0 | 0 | | Validator specs | 609 | 476 | 6 | 9 | 1 | 50 | | Worker specs | 1320 | 941 | 0 | 0 | 0 | 0 | +----------------------+-------+-------+---------+---------+-----+-------+ | Total | 107374 | 79620 | 680 | 3993 | 5 | 17 | +----------------------+-------+-------+---------+---------+-----+-------+ Code LOC: 24188 Test LOC: 55432 Code to Test Ratio: 1:2.3

Slide 16

Slide 16 text

How can we keep our code maintainable?

Slide 17

Slide 17 text

convention |kənˈvɛnʃ(ə)n| 1 a way in which something is usually done: to attract the best patrons the movie houses had to ape the conventions and the standards of theatres. • [ mass noun ] behaviour that is considered acceptable or polite to most members of a society: he was an upholder of convention and correct form | [ count noun ] : the law is felt to express social conventions. 2 an agreement between states covering particular matters, especially one less formal than a treaty. the convention, signed by the six states bordering on the Black Sea, aims to prevent further pollution.

Slide 18

Slide 18 text

ConvenKons are important to ensure that the enKre team speaks the same language

Slide 19

Slide 19 text

the DNSimple wiki

Slide 20

Slide 20 text

the GitHub Ruby style guide h>ps:/ /github.com/styleguide/ruby the Ruby Style Guide h>ps:/ /github.com/bbatsov/ruby-style-guide

Slide 21

Slide 21 text

Rubocop

Slide 22

Slide 22 text

pattern |ˈpat(ə)n| 2 a regular and intelligible form or sequence discernible in the way in which something happens or is done: a complicating factor is the change in working patterns. 4 an excellent example for others to follow: he set the pattern for subsequent study.

Slide 23

Slide 23 text

Text paOerns Homogeneous text and names are easier to search and refactor.

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

Code fragments Recurring behaviors or code fragments can become code snippets or be extracted.

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

PaOerns are useful to promote code reusability improve long-term maintainability and speed up code refactoring

Slide 29

Slide 29 text

4 principles

Slide 30

Slide 30 text

Experiment SomeHmes the code is not ready for extracHon.

Slide 31

Slide 31 text

Stand out When you experiment, write code that the team will noHce.

Slide 32

Slide 32 text

Bonus point: __ is also easy to grep!

Slide 33

Slide 33 text

"Stupid Names" Catch the a>enHon of the other developers.

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

Measure Is a change really introducing quality?

Slide 36

Slide 36 text

Params

Slide 37

Slide 37 text

class ContactParams
 include ParamsAllowed
 
 def self.allowed
 [:label, :first_name, :last_name, :address, :city, :state, :postal_code, :country, :email, :phone]
 end
 end
 
 
 # Params encapsulate the logic to handle some parameters from an untrusted source.
 #
 # A Params object generally doesn't include the Param module directly. Instead, it relies to one of the
 # more specific implementations, such as ParamsAllowed.
 module Params
 def initialize(params)
 @params = ActiveSupport::HashWithIndifferentAccess.new(params.to_h)
 end
 end
 
 
 module ParamsAllowed
 def self.included(base)
 base.include Params
 end
 
 def to_h
 @params.slice(*self.class.allowed).to_hash.symbolize_keys!
 end
 end

Slide 38

Slide 38 text

h>ps:/ /codeclimate.com

Slide 39

Slide 39 text

Fail Experiments may fail!

Slide 40

Slide 40 text

Beware of Mixins

Slide 41

Slide 41 text

Pulling out methods from a large class into mixins is like hiding the dust under the carpet.

Slide 42

Slide 42 text

No content

Slide 43

Slide 43 text

– Anonymous "Any application with an app/concerns directory is concerning."

Slide 44

Slide 44 text

We're not afraid to be mistaken and learn from failed experiments.

Slide 45

Slide 45 text

Abstract Be in control of your code.

Slide 46

Slide 46 text

Custom API Any external dependency we introduce, we wrap it behind a custom API.

Slide 47

Slide 47 text

require 'phony'
 
 module Dnsimple
 
 # Phone is a wrapper for the current phone validation library.
 module Phone
 
 WHITELIST = {
 "+3912312345678" => "+39 12312345678"
 }
 
 # Makes a plausibility check on the number.
 def self.plausible?(number)
 whitelisted?(number) || 
 Phony.plausible?(number)
 end
 
 def self.format(number)
 whitelisted?(number) || 
 Phony.formatted(Phony.normalize(number), parentheses: false)
 end

Slide 48

Slide 48 text

• Side features or extensions can be introduced without an extensive change to the codebase • IncompaHbiliHes can be addressed in a single place • When and if needed, the external dependency can be replaced with li>le effort • TesHng doesn't require intensive stubbing

Slide 49

Slide 49 text

module Dnsimple
 class AliasResolver
 
 class << self
 def adapter
 @adapter ||= NullAdapter.new
 end
 
 def resolve(name)
 adapter.resolve(name)
 end
 
 def enable_test!
 self.adapter = test_adapter.new
 end
 end
 # NullAdapter is a special adapter
 # that discards every resolve request.
 class NullAdapter
 def resolve(name)
 Result.new(name, [], [])
 end
 end
 
 class TestAdapter
 def resolve(*)
 result = @stubs.shift
 result ? Result.new(*result) : Result.new
 end
 end
 
 class GoAdapter
 BIN = File.join(Rails.root, "bin", "dsalias")
 
 def resolve(name)
 #
 end
 end


Slide 50

Slide 50 text

Dnsimple::AliasResolver.enable_test!
 
 describe "a methods that interacts with the resolver" do
 before do
 AliasResolver.adapter.test :stub, "fully.qualified.host"
 end
 
 it "does something" do
 # ...
 end
 end


Slide 51

Slide 51 text

AcKveRecord We have special convenHons and guidelines.

Slide 52

Slide 52 text

• Methods defined in ActiveRecord::Base are not allowed outside the Models • Models MUST expose custom API to perform operaHons • Callbacks are allowed only for data integrity • Query methods in ActiveRecord::Base are not allowed outside the Models or Finders • Scopes can't be invoked directly outside Models or Finders and they exist only to support Finders

Slide 53

Slide 53 text

require 'model_finder'
 
 class SuffixFinder
 include ModelFinder::Finder
 
 def self.find(identifier)
 find_by_tld(identifier)
 end
 
 def self.find!(identifier)
 find(identifier) or not_found!(identifier)
 end
 
 def self.find_by_tld(identifier)
 tld = identifier.to_s.downcase
 enabled.where(tld: tld).take
 end
 
 def self.suffix_listing
 scope.enabled
 end
 def self.newgtld_suffixes
 scope.new_tlds.enabled.order_alpha
 end
 
 def self.gtld_suffixes
 scope.global_tlds.enabled.order_alpha
 end
 
 def self.cctld_suffixes
 scope.cc_tlds.enabled.order_alpha
 end
 
 def self.privacy_supported_tlds
 scope.where(whois_privacy: true).pluck(:tld)
 end
 
 private
 
 def self.not_found!(identifier)
 raise(ActiveRecord::RecordNotFound, "TLD `#{identifier}` not found")
 end
 
 end


Slide 54

Slide 54 text

These guidelines are the result of years of experiments, discussions and refactoring.

Slide 55

Slide 55 text

No content

Slide 56

Slide 56 text

No content

Slide 57

Slide 57 text

h>p:/ /hanamirb.org

Slide 58

Slide 58 text

module Controllers::Domains
 class Create
 def call(params)
 @result = DomainCreateCommand.execute(
 command_context, 
 this_account, 
 DomainParams.new(params))
 @domain = @result.data
 
 if @result.successful?
 render DomainSerializer.new(@domain), 201
 else
 render ErrorSerializer.new(@result.error, @domain) end
 end
 end
 end class DomainHostingController
 def create
 @result = DomainCreateCommand.execute(
 command_context,
 this_account,
 domain_params)
 @domain = @result.data
 
 if @result.successful?
 redirect_to domain_path(@domain)
 else
 format.html { render action: "new" }
 end
 end
 end

Slide 59

Slide 59 text

module Api::V2
 module Controllers::RegistrarWhoisPrivacy
 
 class Enable
 include Hanami::Action
 before :require_subscription!, :require_good_standing!
 
 def call(params)
 # code
 end
 end
 
 class Disable
 include Hanami::Action
 before :require_subscription!
 
 def call(params)
 # code
 end
 end
 
 end
 end


Slide 60

Slide 60 text

Wrapping up

Slide 61

Slide 61 text

Code Monkey Don't be a

Slide 62

Slide 62 text

Experiment Don't take my word for it

Slide 63

Slide 63 text

Fail It's ok. Be ready to fail and learn from failures.

Slide 64

Slide 64 text

Small steps Don't throw away your enHre codebase

Slide 65

Slide 65 text

Code Smells Pay a>enHon to

Slide 66

Slide 66 text

Programming Languages Get influenced by other

Slide 67

Slide 67 text

Start today!

Slide 68

Slide 68 text

Credits • h>ps:/ /www.flickr.com/photos/goya74/8584790873 • h>ps:/ /www.flickr.com/photos/81723459@N04/22559333751/

Slide 69

Slide 69 text

Thanks! DNSimple dnsimple.com @dnsimple Simone Carle4 simonecarle4.com @weppos