167 Software design basics [4d]

Updated . Posted . Visible to the public.

Reading

Read the following chapters from The Pragmatic Programmer, anniversary edition (in our library):

  • Chapter 1, Topic 3: Software Entropy
  • Chapter 2, Topic 9: The Evils of Duplication
  • Chapter 2, Topic 10: Orthogonality
  • Chapter 5, Topic 28: Decoupling (and the Law of Demeter)

Read the following chapters from Clean Code (in our library):

  • Chapter 1: Clean Code
  • Chapter 2: Meaningful Names
  • Chapter 3: Functions
  • Chapter 4: Comments
  • Chapter 5: Formatting
  • Chapter 8: Boundaries
  • Chapter 10: Classes
  • Chapter 12: Emergence
  • Chapter 17: Smells and Heuristics

Also read:

Read the following chapters from our book Growing Rails Application in Practice:

    1. Dealing with fat models
    1. Extracting service objects

Discuss with your mentor what you took away from each topic.

Rules of thumb

Apart from the concepts mentioned above, there are a many more rules that are good to follow most of the time. For example:

  • Embrace Locality: Avoid cluttering one concept across the entire code base. It should be encapsulated in single files or folders.
  • Be easy to call: A service should always try to have a simple public interface, even if that causes additional complexity within the class. For example:
    • Interacting with the model should be easy for controllers and views
    • Interacting with helpers and routes should be easy for views
  • Avoid long instruction manuals: If you need to write down an A4-letter for your colleagues on how to use your service, there might be a way to refactor it to a simpler public interface. A post-it sized note should be enough, if anything!

Exercise

We're building an e-commerce app where users can create and view invoices.

This is our current model:

class Invoice < ApplicationRecord
  has_many :items
  validates_presence_of :recipient_address, :number
end

class Invoice::Item < ApplicationRecord
  belongs_to :invoice
  belongs_to :product
  validates_numericality_of :units
end  

class Product < ApplicationRecord
  validates_presence_of :description
  validates_numericality_of :unit_price
end

This is a view that shows an invoice:

%h1
  Invoice
  = @invoice.number

%h2 Recipient
= @invoice.recipient_address

%h2 Items

%table
  %tr
    %th Description
    %th Quantity
    %th Item total

  - @invoice.items.each do |item|
    %td= item.product.description
    %td= item.units
    %td= item.units * item.product.unit_price
    
  %tr
    %th
      Invoice total
    %td(colspan=3)
      = @invoice.items.sum { |item| item.units * item.product.unit_price } * 1.19

How would you judge the quality of this code?
Try to apply what you learned with a refactoring of the model and the view. What are the advantages of your solution?

Exercise: MovieSearch vs. Authorization

Let's say there is the a MovieSearch class in your project with the following public API:

class MovieSearch
  def initialize(query)
    @query = query
  end
  
  def results
    Movie.where('title LIKE ?', @query)
  end
end
search = MovieSearch.new('Interstellar')
search.results.each do |movie|
  puts movie.title
end

Now the current user should not be allowed to search all movies, but only a subset based on their role and the movie's state. A naive inline authorization could look like this:

class MovieSearch
  def initialize(query, current_user)
    @query = query
    @current_user = current_user
  end
  
  def results
    scope = Movie.where('title LIKE ?', @query)
    unless current_user.moderator?
      scope = scope.where('state = "approved" OR user_id = ?', @current_user.id)
    end
    scope
  end
end

Try to come up with an alternative implementation where the two concepts (movie search and authorization) are less coupled.

Henning Koch
Last edit
Michael Leimstädtner
License
Source code in this card is licensed under the MIT License.
Posted by Henning Koch to makandra Curriculum (2015-07-07 15:22)