menu
Bloatersarrow_forward_ios
Long Method
zoom_outLong Method

Overview

A long method is a method that is too long or too complex. It's often considered a code smell or anti-pattern in software development because it makes the code harder to understand, maintain, and debug. Long methods tend to violate the principles of modularity, readability, and maintainability.

Problems

  • Readability-long methods are difficult to read and comprehend, especially for developers who are not familiar with the codebase. It becomes challenging to understand the logic and flow of the program.
  • Maintenance-making changes to a long method is risky because of its size and complexity. Even small modifications can inadvertently introduce bugs or unintended consequences, making maintenance difficult and error-prone.
  • Reusability-Long methods often contain multiple functionalities tightly coupled together. This makes it harder to reuse parts of the code in other contexts or refactor it for improved efficiency.
  • Testing-Long methods are challenging to test comprehensively. Writing test cases to cover all possible execution paths becomes cumbersome, increasing the likelihood of missing edge cases or bugs.
  • Scalability-Long methods hinder the scalability of the codebase. As the project grows, maintaining and extending such methods becomes increasingly cumbersome, leading to a degradation in development speed and efficiency.

Real World Example

As an example, let's take a method that deals with creating new user.
class User::Create
  attr_reader :params

  def initialize(params)
    @params = params
    @user = User.new
  end
  
  def call
    puts("User creation started!")
    puts("Params provided: #{params}")
  
    raise("Email not provided") if params[:email].nil?
    raise("First Name not provided") if params[:first_name].nil?
    raise("Last Name not provided") if params[:last_name].nil?
    
    user.assign_attributes(
      name: "#{params[:first_name]} #{params[:last_name]}",
      email: params[:email],
      is_admin: params.fetch(:is_admin, false)
    )
  
    if user.valid?
      user.save
    else
      UserMailer.greetings(user: user).deliver_now
    end
  end
end
As we can see, this method contains many functionalities that we could move to separate methods. Considering the above code, we can distinguish several actions:
  • displaying logs
  • params validation
  • building user model
  • saving user
  • sending notification
class User::Create
  attr_reader :params

  def initialize(params)
    @params = params
    @user = User.new
  end
  
  def call
    display_logs
    validate!
    build_user
    user.save || send_notification
  end

  private
  
  def display_logs
    puts("User creation started!")
    puts("Params provided: #{params}")
  end

  def validate!
    raise("Email not provided") if params[:email].nil?
    raise("First Name not provided") if params[:first_name].nil?
    raise("Last Name not provided") if params[:last_name].nil?
  end

  def build_user
    user.assign_attributes(
      name: "#{params[:first_name]} #{params[:last_name]}",
      email: params[:email],
      is_admin: params.fetch(:is_admin, false)
    )
  end

  def send_notification
    UserMailer.greetings(user: user).deliver_now
  end
end
By moving the code to individual previously isolated methods, we are able to create a method that is much more transparent.

Pros & Cons

Pros

  • Readability-breaking down a long method into smaller functions improves code readability. Each function has a clear purpose, making it easier for developers to understand what the code does.
  • Maintainability-smaller functions are easier to maintain. Changes or updates to a specific functionality can be made within the scope of a single function, reducing the risk of introducing bugs and making the codebase more manageable.
  • Testability-smaller functions are easier to test because they typically have a single responsibility and are more focused. Writing unit tests for individual functions allows for more thorough testing of each component, improving overall test coverage.
  • Reusability-Refactoring code into smaller functions promotes code reuse. Functions that perform specific tasks can be easily reused in other parts of the application, reducing code duplication and improving modularity.
  • Scalability-breaking down a long method into smaller functions helps manage the complexity of the codebase as it grows. It allows developers to add new features or make changes without significantly increasing the overall complexity of the system.

Cons

  • Overabstraction-breaking down code into too many small functions can lead to overabstraction, where the code becomes fragmented and difficult to follow. Developers must strike a balance between breaking down functions and keeping the code cohesive and understandable.
  • Function Call Overhead-introducing multiple function calls can incur a slight performance overhead compared to a single long method. While this overhead is usually negligible, it's something to consider in performance-critical applications.
  • Name Choice Complexity-as the number of functions increases, choosing meaningful and descriptive names for each function becomes more challenging. Developers need to ensure that function names accurately reflect their purpose without becoming overly verbose.
  • Indirection-breaking down code into smaller functions can introduce indirection, making it harder to follow the flow of execution, especially if there are many function calls nested within each other.