Clean Code Cheatsheet: A Developer’s Guide to Readable and Maintainable Code with Ruby Examples
Published: 2024-03-05 19:55:33

Welcome to this short Clean Code Cheatsheet — your comprehensive guide to writing clean, readable, and maintainable code, with practical examples in the Ruby programming language. Whether you’re a Ruby enthusiast or simply looking to enhance your coding practices, this cheatsheet covers essential clean code principles applicable to any language.

Why Clean Code?
Clean code is the cornerstone of effective software development. It’s about crafting code that is not only functional but also easy to understand, modify, and collaborate on. In the world of programming, code is read far more often than it is written. Clean code prioritizes readability, simplicity, and good design to minimize the long-term cost of maintaining and evolving a codebase.

What to Expect
This cheatsheet provides actionable tips and guidelines for writing clean code, accompanied by Ruby examples. Each section covers a specific aspect of clean coding, offering insights that can be applied across various programming languages.

Meaningful Names
Choose descriptive and expressive variable names

`# Bad
a = 10
b = 'John'
x = 15

# Good
age = 10
first_name = 'John'
number_of_students = 15`

Methods
Craft concise and focused functions
`
# Bad
def process_data_and_output_result(input)
  # complex data processing
  # ...
  # complex result formatting
  # ...
end

# Good
def process_data(input)
  processed_data = complex_data_processing(input)
  formatted_result = format_result(processed_data)
  output_result(formatted_result)
end

def complex_data_processing(data)
  # detailed logic
end

def format_result(result)
  # detailed formatting
end

def output_result(result)
  # output logic
end`

Method arguments
Limit the number of arguments

`# Bad - Passing individual parameters
def book(user, hotel)
  # ...
end

user = User.new('John')
hotel = Hotel.new('Hotel')
book(user, hotel)

# Good - Passing a class object
def book(booking_info)
  # ...
end

class BookingInfo
  attr_accessor :user, :hotel

  def initialize(user, hotel)
    @user = user
    @hotel = hotel
  end
end

user = User.new('John')
hotel = Hotel.new('Hotel')

booking_info = BookingInfo.new(user, hotel)
book(booking_info)`

Constants
Create constants to avoid the hardcoding things

`# Bad
if error_code == 8097
 # Handle the service unavailable error
end

# Good
SERVICE_UNAVAILABLE_ERROR_CODE = 8097

if error_code == SERVICE_UNAVAILABLE_ERROR_CODE
  # Handle the service unavailable error
end`

Mental mapping
Avoid the need to keep things on our mind

`# Bad
u_n = 'John'
users = ['John', 'Peter']

users.each do |u|
  first_operation(u)
  second_operation
  ..
  n_operation(u)
end

# Good - Using a descriptive loop variable
target_user_name = 'John'
users = ['John', 'Peter']

users.each do |current_user|
  first_operation(current_user)
  second_operation
  # ...
  n_operation(current_user)
end`

Comments
Use comments sparingly, focusing on why, not what:

`# Bad 
# Increment i by 1 
i += 1  

# Good 
i += 1  # Increment loop counter`

Formatting
Follow Code standard formatting conventions.

If you are working with Ruby, try to follow the standard formatting conventions. Refer to the RubyStyle Guide for comprehensive guidelines.

Utilize tools like RuboCop to enforce and maintain the code style

`# Bad
def my_method(param1, param2)
result = param1 + param2
return result
end

# Good
def my_method(param1, param2)
  result = param1 + param2
  return result
end`

Error Handling
Use exceptions for error handling.

`# Bad
def divide(a, b)
  if b == 0
    puts 'Cannot divide by zero!'
    return
  end
  a / b
end

# Good
class DivisionByZeroError < StandardError
  def initialize(msg = 'Cannot divide by zero!')
    super
  end
end

def divide(a, b)
  raise DivisionByZeroError if b.zero?
  a / b
end`

Duplicated Code
Eliminate redundancy

`# Bad
def calculate_area_of_circle(radius)
  Math::PI * radius * radius
end

def calculate_volume_of_sphere(radius)
  (4 / 3) * Math::PI * radius * radius * radius
end

# Good
def calculate_area_of_circle(radius)
  Math::PI * radius * radius
end

def calculate_volume_of_sphere(radius)
  (4 / 3.0) * calculate_area_of_circle(radius) * radius
end`

Encapsulate conditionals
Always aim for self-explanatory method names to improve the understanding of the code.

`# Bad
if user_signed_in? && admin_user?
  # ...
end

# Good
def admin_access?(user)
  user_signed_in? && admin_user?(user)
end

if admin_access?(current_user)
  # ...
end`

Avoid negative conditionals
Writing code with positive conditionals instead of negative ones can greatly enhance code readability and maintainability. Code that is expressed in a positive manner tends to be more straightforward and easier for others (or even yourself) to understand.

`# Bad
unless !user.active && !user.admin?
  # Perform actions for inactive non-admin users
end

# Good
if user.inactive? && !user.admin?
  # Perform actions for inactive non-admin users
end`

Using Polymorphism to Improve Switch Statements
Switch statements can become hard to maintain and extend as the number of cases grows. Leveraging polymorphism provides a more flexible and scalable alternative. Let’s explore how to improve a switch statement using polymorphism with an example.

`# Bad

class Shape
  def draw
    case type
    when :circle
      draw_circle
    when :square
      draw_square
    when :triangle
      draw_triangle
    else
      raise 'Unsupported shape!'
    end
  end

private

  def draw_circle
    # draw circle logic
  end

  def draw_square
    # draw square logic
  end

  def draw_triangle
    # draw triangle logic
  end
end

shape = Shape.new(:circle)
shape.draw


# Good

class Shape
  def draw
    raise NotImplementedError, 'Subclasses must implement this method'
  end
end

class Circle < Shape
  def draw
    # draw circle logic
  end
end

class Square < Shape
  def draw
    # draw square logic
  end
end

class Triangle < Shape
  def draw
    # draw triangle logic
  end
end

shape = Circle.new
shape.draw
Objects and Data Structures
Encapsulate data with behavior in classes, and use data structures for simple storage

# Bad
user_data = { name: 'Alice', age: 25 }

# Good
class User
  attr_accessor :name, :age
  def initialize(name, age)
    @name = name
    @age = age
  end
end`

SOLID Principles
Follow SOLID principles for designing maintainable and scalable systems

Single Responsibility Principle (SRP): The Single Responsibility Principle states that a class should have only one reason to change. In this example, the Calculator class has a single responsibility, which is to perform addition. If there are changes related to addition, the Calculator class is the only one that needs modification.

`class Calculator
  def add(a, b)
    a + b
  end
end`

Open/Closed Principle (OCP) : It states that a class should be open for extension but closed for modification.

`class Shape
  def area
    raise NotImplementedError, 'Subclasses must implement this method'
  end
end

class Circle < Shape
  def area(radius)
    Math::PI * radius * radius
  end
end`

The Shape class is open for extension by allowing new shapes to be added through subclasses (like Circle) without modifying the existing Shape class.

Liskov Substitution Principle (LSP): The Liskov Substitution Principle states that objects of a superclass should be able to be replaced with objects of a subclass without affecting the correctness of the program.

`class Shape
  def area
    raise NotImplementedError, 'Subclasses must implement this method'
  end
end

class Rectangle < Shape
  attr_accessor :width, :height

  def area
    width * height
  end
end

class Square < Shape
  attr_accessor :side

  def area
    side * side
  end
end

def calculate_total_area(shapes)
  total_area = 0
  shapes.each { |shape| total_area += shape.area }
  total_area
end

rectangle = Rectangle.new
rectangle.width = 5
rectangle.height = 10

square = Square.new
square.side = 7

shapes = [rectangle, square]

puts "Total area: #{calculate_total_area(shapes)}"`

Interface Segregation Principle (ISP): The Interface Segregation Principle states that a class should not be forced to implement interfaces it does not use.

`# Bad - Interface with multiple methods
module Employee
  def work
    # work logic
  end

  def manage
    # management logic
  end
end

class Worker
  include Employee

  # Worker needs to implement both work and manage, even if it doesn't manage
end

class Manager
  include Employee

  # Manager needs to implement both work and manage, even if it doesn't perform the regular work
end

# Good - Separate interfaces for work and management
module Workable
  def work
    # work logic
  end
end

module Managable
  def manage
    # management logic
  end
end

class Worker
  include Workable

  # Worker only includes the Workable interface, as it doesn't manage
end

class Manager
  include Workable
  include Managable

  # Manager includes both interfaces, as it performs both work and management
end`

Here, each class has a specific interface relevant to its responsibilities. Workers implement the work interface, while Managers implement the manage interface.

Dependency Inversion Principle (DIP): The Dependency Inversion Principle states that high-level modules should not depend on low-level modules, but both should depend on abstractions.
`
class LightBulb
  def turn_on
    # turn on logic
  end
end

class Switch
  def initialize(device)
    @device = device
  end
  def operate
    @device.turn_on
  end
end`

he Switch class depends on the abstraction (LightBulb) rather than the specific implementation. This allows for flexibility and easier maintenance.

Testing
Descriptive Test Names: To help developers understand the purpose and expected behavior of each test case without diving into the details.
`
# Bad
def test_method
  # ...
end

# Good
def test_user_authentication_success
  # ...
end
Explicit assertions: Use explicit assertions that convey the expected outcome of the test, reducing ambiguity and making it clear what the test is checking.

# Bad
assert_equal true, user.authenticate('password')

# Good
assert user.authenticate('password'), 'User authentication failed'`

This cheatsheet provides a foundation for writing clean code in any programming language, using Ruby as a practical example. Dive into each section for more detailed guidance and examples. Let’s make our code not only functional but a joy to read and maintain.

Happy coding!