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)
param1 + param2
end
# Good
def my_method(param1, param2)
param1 + param2
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 clear and 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
The 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!
Top comments (0)