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

An Optimistic Proposal for Making Horrible Code Bearable, V2

An Optimistic Proposal for Making Horrible Code Bearable, V2

How do we cope with really bad code?

How can we give our teams the confidence to work with legacy code that we don’t fully understand?

How can we stop the bleeding when we don’t have time to stop shipping?

The attempted rewrite is over, the dust has settled, and the monolith isn’t going away. After all, it’s still the app that makes all the money. On the other hand, nobody wants to work on it, every new feature takes forever, and your entire team is afraid of making any change for fear of the whole thing collapsing in on itself.

Join us for an exclusive event featuring Joe Mastey. During this event, we’ll walk through some of the technical and social problems that arise from difficult codebases. We’ll learn to stop making things worse, to measure what we need to change, and start making progress.

Joseph Mastey

June 01, 2017
Tweet

More Decks by Joseph Mastey

Other Decks in Programming

Transcript

  1. @jmmastey | http://bit.ly/2rw1W8I • Custom system packages (my-libssl) • 6000+

    line User model • Four days to run test suite, with 10% flapping tests. • 1,000,000+ lines of ruby
  2. @jmmastey | http://bit.ly/2rw1W8I … 220 other files here 339 ./lib/devise/models/confirmable.rb

    347 ./test/integration/recoverable_test.rb 350 ./test/models/lockable_test.rb 363 ./test/integration/registerable_test.rb 499 ./lib/devise.rb 513 ./lib/devise/rails/routes.rb 519 ./test/models/confirmable_test.rb 698 ./test/integration/authenticatable_test.rb 16262 total
  3. @jmmastey | http://bit.ly/2rw1W8I +----------------------+-------+-------+---------+---------+-----+-------+ | Name | Lines | LOC

    | Classes | Methods | M/C | LOC/M | +----------------------+-------+-------+---------+---------+-----+-------+ | Controllers | 3347 | 2570 | 71 | 310 | 4 | 6 | | Helpers | 432 | 347 | 0 | 49 | 0 | 5 | | Models | 1957 | 1451 | 51 | 200 | 3 | 5 | | Mailers | 85 | 71 | 3 | 9 | 3 | 5 | | Javascripts | 9344 | 6447 | 0 | 724 | 0 | 6 | | Libraries | 2196 | 1532 | 27 | 143 | 5 | 8 | | Controller specs | 1675 | 1237 | 0 | 5 | 0 | 245 | | … | View specs | 283 | 236 | 0 | 0 | 0 | 0 | +----------------------+-------+-------+---------+---------+-----+-------+ | Total | 24956 | 18451 | 153 | 1449 | 9 | 10 | +----------------------+-------+-------+---------+---------+-----+-------+ Code LOC: 12418 Test LOC: 6033 Code to Test Ratio: 1:0.5
  4. @jmmastey | http://bit.ly/2rw1W8I +----------------------+-------+-------+---------+---------+-----+-------+ | Name | Lines | LOC

    | Classes | Methods | M/C | LOC/M | +----------------------+-------+-------+---------+---------+-----+-------+ | Controllers | 3347 | 2570 | 71 | 310 | 4 | 6 | | Helpers | 432 | 347 | 0 | 49 | 0 | 5 | | Models | 1957 | 1451 | 51 | 200 | 3 | 5 | | Mailers | 85 | 71 | 3 | 9 | 3 | 5 | | Javascripts | 9344 | 6447 | 0 | 724 | 0 | 6 | | Libraries | 2196 | 1532 | 27 | 143 | 5 | 8 | | Controller specs | 1675 | 1237 | 0 | 5 | 0 | 245 | | … | View specs | 283 | 236 | 0 | 0 | 0 | 0 | +----------------------+-------+-------+---------+---------+-----+-------+ | Total | 24956 | 18451 | 153 | 1449 | 9 | 10 | +----------------------+-------+-------+---------+---------+-----+-------+ Code LOC: 12418 Test LOC: 6033 Code to Test Ratio: 1:0.5
  5. @jmmastey | http://bit.ly/2rw1W8I +----------------------+-------+-------+---------+---------+-----+-------+ | Name | Lines | LOC

    | Classes | Methods | M/C | LOC/M | +----------------------+-------+-------+---------+---------+-----+-------+ | Controllers | 3347 | 2570 | 71 | 310 | 4 | 6 | | Helpers | 432 | 347 | 0 | 49 | 0 | 5 | | Models | 1957 | 1451 | 51 | 200 | 3 | 5 | | Mailers | 85 | 71 | 3 | 9 | 3 | 5 | | Javascripts | 9344 | 6447 | 0 | 724 | 0 | 6 | | Libraries | 2196 | 1532 | 27 | 143 | 5 | 8 | | Controller specs | 1675 | 1237 | 0 | 5 | 0 | 245 | | … | View specs | 283 | 236 | 0 | 0 | 0 | 0 | +----------------------+-------+-------+---------+---------+-----+-------+ | Total | 24956 | 18451 | 153 | 1449 | 9 | 10 | +----------------------+-------+-------+---------+---------+-----+-------+ Code LOC: 12418 Test LOC: 6033 Code to Test Ratio: 1:0.5
  6. @jmmastey | http://bit.ly/2rw1W8I def methods_per_activerecord_class children = ObjectSpace.each_object(Class) .select {

    |klass| klass < ActiveRecord::Base } sorted = children.sort_by { |klass| klass.methods(false).count } sorted.reverse! sorted.map { |klass| "#{klass.methods(false).length} #{klass}" } end
  7. @jmmastey | http://bit.ly/2rw1W8I 75 Business, 74 User, 59 Product, 53

    Group, 48 JournalEntry, 37 Notification, 30 Order, 30 Subscription, 22 Audited::Audit, 12 CombinedPurchaseRecord, 8 PgSearch::Document
  8. @jmmastey | http://bit.ly/2rw1W8I # arrowhead of dooooom Metrics/BlockNesting: Max: 4

    # short methods, man... Metrics/MethodLength: Max: 414 Metrics/LineLength: Max: 324 # fewer parameters Metrics/ParameterLists: Max: 15
  9. @jmmastey | http://bit.ly/2rw1W8I # ratchets/models_spec.rb ar_classes = ObjectSpace.each_object(Class) .select {

    |klass| klass < ApplicationRecord } METHOD_THRESHHOLD = 505 ar_classes.each do |klass| describe klass do it "shouldn't have more than #{METHOD_THRESHHOLD} methods" do expect(klass.methods(false).count).to be < METHOD_THRESHHOLD end end end
  10. @jmmastey | http://bit.ly/2rw1W8I 1SJPSJUJ[F • make your tests better •

    use less magic • cut off the head • prioritize high churn code
  11. @jmmastey | http://bit.ly/2rw1W8I strategy = case user.payroll_strategy when 'biweekly' then

    BiweeklyPayrollStrategy when 'fortnightly' then FortnightlyPayrollStrategy when 'monthly' then MonthlyPayrollStrategy end strategy.new(user, params) strategy.dispatch
  12. @jmmastey | http://bit.ly/2rw1W8I # POST /api/users/create_from_facebook def create_from_facebook service =

    RegistersUserFromFacebook.new(params[:authentication_token]) service.call @user = service.user if service.errors.empty? render status: :created, location: logged_in_profile_url else render json: { message: service.message, errors: service.errors }, status: :unprocessable_entity end end
  13. @jmmastey | http://bit.ly/2rw1W8I class RegistersUserFromFacebook attr_reader :token, :user, :errors, :message

    def initialize(token) @token = token @errors = [] end def call # 200 lines of doom here end end
  14. @jmmastey | http://bit.ly/2rw1W8I 0UIFS3FTPVSDFT • Refactoring Ruby (Jay Fields) •

    Why Programs Fail: A Guide to Systematic Debugging (Andreas Zeller) • Service Objects in Ruby • Gems: • rubocop / hound ci • rspec-activerecord-formatter • bundler-stats UIBOLT