Judging design quality in ruby on rails


Judging Design Quality

Effective object-oriented design (OOD) in Ruby on Rails is crucial for maintaining scalable, efficient, and less costly applications. Contrary to historical metrics like SLOC (source lines of code), modern Ruby developers rely on more sophisticated methods to evaluate code quality, particularly focusing on OOD principles.

Misleading Metrics: SLOC

Historically, the quantity of code—measured by SLOC—was a misguided attempt to gauge productivity.

Wikipedia page about SLOC

However, this metric often penalizes efficient programming and advantages verbose, inefficient coding practices. Consider the following example:

# app/models/user.rb
# Columns:
# id (integer, PK)
# name (string, indexed)
# email (string, unique indexed)
class User < ApplicationRecord
  def display_name
    name.blank? ? "No Name Provided" : name
  end
end

While the display_name method above is concise and efficient, a SLOC-focused metric might mistakenly undervalue the programmer’s effort compared to a less concise approach.

Current Metrics: OOD Principles

Modern Ruby tools assess code based on OOD principles, focusing on characteristics like modularity, encapsulation, and polymorphism.

These tools, however, should be used cautiously. High scores on OOD metrics don’t necessarily guarantee future ease of changes. For instance, over-engineering for potential, future requirements can lead to expensive adjustments when needs change. Below illustrates a bad versus a better OOD approach in a Rails model context:

Bad design: Over-anticipating future requirements

# app/models/project.rb
class Project < ApplicationRecord
  has_many :tasks

  # Over-designed status management:
  # This type of nonsense happens when teams can't seem to understand that
  # maybe the initial requirements could've been simplified.
  def activate
    update(status: 'active') if status == 'initialized'
  end

  def finalize
    update(status: 'finished') if status == 'done'
  end
end

Better design: Simplified and direct, just make sure to validate the statuses.

class Project < ApplicationRecord
  has_many :tasks

  # If this validator fails, the error will explain why, and future changes and errors are encapsulated here.
  enum status: [ 'active', 'finished', 'failed' ]
end

class ProjectsController < ApplicationController
  def update
    @project.update(status: status)
  end
end

In the above, the “bad” design preempts specific status changes, leading to rigid code susceptible to future changes.

The “better” design is simple and adaptable, maintaining ease of modification.

Utilizing OOD Metrics Wisely

OOD metrics are undeniably useful for identifying problematic code areas, but they’re not direct quality indicators.

They serve better as proxies, suggesting areas for deeper investigation. Below is an example of how metrics might indicate a problem:

# Running an OOD metrics report might reveal high coupling in this class:
# app/models/invoice.rb
# Columns: id (integer, PK), order_id (integer, FK, indexed), issued_on (date)
class Invoice < ApplicationRecord
  belongs_to :order
  # High coupling: Invoice class knows too much about Order and its internals
  def total
    order.line_items.sum(&:price) * (1 - order.discount)
  end
end

The coupling shown above could direct developers to refactor towards better encapsulation and reduced interdependencies.

Recomended refactor

# app/models/invoice.rb
class Invoice < ApplicationRecord
  belongs_to :order
  has_many :items, through: :order
  delegate :total, to: :order
end

# works
@invoice.total

# app/models/order.rb
class Order < ApplicationRecord
  has_many :invoices
  has_many :items, class_name: Orders::Items.to_s
  delegate :total_price, to: :items, prefix: :items

  def total
    items_total_price * (1 - order.discount)
  end
end

# works
@order.total

# app/models/items.rb
class Orders::Items < ApplicationRecord
  belongs_to :order
  scope :total_price, -> { sum(:price) }
end

# works and returns the sum using and SQL statement.
@orders_items.total_price

NOTE: Here the model Orders::Items could be subject to denormalization depending on system requirements.

Concluding Thoughts

Ultimately, the best metric might be “cost per feature” over a relevant time interval, balancing immediate needs against future costs—akin to managing technical debt.

Developers must use OOD metrics judiciously, employing them as a guide rather than a definitive measure, and always with a focus on the specific goals and context of their application.


Related posts

Free Training By Email:
Building a Rails Engine

We will send udpates but we care about your data.
No spams.

Back to homepage