A clean repository dedicated to share Clean Code techniques, hints, didactic examples, realiable references and signals that the code is dirty.
It is a software development philosophy that consists in applying techniques that make the code...
| More | Easy to | Ultimately More |
|---|---|---|
| β Clear | β Read | β Reusable |
| β Intuitive | β Modify | β Maintanable |
| β Reliable | β Review | β Resistant to Technical Debts π οΈ (TDs) |
| β Understandable | β Maintain | β Resistant to Bugs π |
Note
"Clean Code: A Handbook of Agile Software Craftsmanship" (2008)
β Robert C. Martin
Code Smells
- Rigidity: No change is trivial; each change in the code adds more twists and tangles.
- Complexity: Changes require a lot of research for understanding and implementation.
- Fragility: Changes break existing parts of the code.
- Immobility: You can't reuse existing parts of the code.
It's when you have a system that displays the user's name on the screen, and you're going to implement the code that shows the surname, but you had to change the code that shows the age, nationality, CPF, the one that calculates the next prime number, and the one that discovers the cure for cancer.
RigidityNote
"Business changes. Technology changes. The team changes. Team members change. The problem is not the change itself, because change will happen; the problem, in fact, is the inability to cope with change when it arrives."
β Kent Beck
It's when the code for sorting a list uses the code for sorting a queue and then reverses the result, making it work magically (WTF metric)
ComplexityNote
"Complexity kills. Complexity destroys the developer's life. Complexity makes the product hard to plan, build, and test."
β Ray Ozzie (CTO of Microsoft)
Note
"It is harder to read the code than to write it."
β Joel Spolsky (Creator and CEO of Stack Overflow)
It's when you fix the login and break the registration.
FragilityNote
"If a change in requirements breaks your architecture, then your architecture is crap."
β Robert C. Martin
Note
"If you are afraid to change something, then it is poorly designed."
β Martin Fowler
It's when you want to implement a code that builds a table but can't reuse an existing code that builds a table that is 90% similar.
ImobilityNote
"Code reuse is the Holy Grail of Software Engineering."
β Douglas Crockford
There are various clean code techniques available! π
Here are some of them...
Warning
The didactic examples are written in Ruby language to convey the idea in a simple and didactic way, but keep in mind that these Clean Code techniques work in any programming language.
Don't Repeat Yourself
- If a piece of code is identical or very similar to another, try to extract it into a generalized function
- parameters are your friends
Note
"Duplication is the primary enemy of a well-designed system. It represents extra work, extra risk, and unnecessary extra complexity."
β Robert C. Martin
Note
"I think one of the most valuable rules is to avoid duplication. Once and only once."
β Martin Fowler
- Before DRY
def greet_morning puts "Good morning, Alice!" end def greet_afternoon puts "Good afternoon, Alice!" end def greet_evening puts "Good evening, Alice!" end greet_morning greet_afternoon greet_evening
- After DRY
def greet(day_period, name) puts "Good #{day_period}, #{name}!" end greet("morning", "Alice") greet("afternoon", "Alice") greet("evening", "Alice")
Keep It Simple Stupid
- Try to make the code so "stupid" that a 5-year-old could understand it.
Note
"A difference between a smart programmer and a professional programmer is that the professional understands that clarity is what matters. π Professionals use their powers for good and write code that others can understand."
β Robert C. Martin
Note
"Simple can be harder than complex. You have to work hard to get your thinking clean and simple. But it's worth it in the end, because once you get there, you can move mountains."
β Steve Jobs
- Before KISS
def calculate_discount(price) if price > 100 if price < 200 discount = 10 else discount = 20 end else discount = 0 end discounted_price = price - (price * discount / 100) return discounted_price end puts calculate_discount(150) puts calculate_discount(50) puts calculate_discount(250)
- After KISS
def calculate_discount(price) discount = case price when 0..100 then 0 when 101..200 then 10 else 20 end price - (price * discount / 100) end puts calculate_discount(150) puts calculate_discount(50) puts calculate_discount(250)
You Ainβt Gonna Need It
- Don't build a cannon to kill a fly; you might not even need it afterward.
Note
"90% of the features for the future are never used."
β Don Wells (Extreme Programming)
Note
"YAGNI applies only to the effort of making the software support future and/or hypothetical functionality; it does not apply to the effort of making the software easier to change."
β Martin Fowler
Tip
Use The Combo YAGNI β KISS β DRY
Do the Necessary β Do the Simple β Do the Unique
- Before YAGNI
class User attr_accessor :name, :email, :age, :address, :phone_number def initialize(name, email) @name = name @email = email @age = nil # Unused feature @address = nil # Unused feature @phone_number = nil # Unused feature end def send_welcome_email puts "Welcome, #{name}! A welcome email has been sent to #{email}." end end user = User.new("Alice", "alice@example.com") user.send_welcome_email
- After YAGNI
class User attr_accessor :name, :email, :age, :address, :phone_number def initialize(name, email) @name = name @email = email end def send_welcome_email puts "Welcome, #{name}! A welcome email has been sent to #{email}." end end user = User.new("Alice", "alice@example.com") user.send_welcome_email
Single Responsibility Principle
- Separate the code into simple, well-defined, well-intentioned tasks and give clear names.
- Prevents "spaghetti code" π
Note
"Functions should do one thing, do it well, and do only that."
β Robert C. Martin
- Before SRP
class ReportGenerator def generate_report(data) # Process the data processed_data = data.map { |item| Processor.process(item) } # Convert data to JSON json_data = processed_data.to_json # Save the report to a file File.open('report.json', 'w') do |file| file.write(json_data) end # Send the report via email Email.send(data: json_data) end end data = ["a", "b", "c", "d"] report = ReportGenerator.new report.generate_report(data)
- After SRP
class ReportGenerator def generate_report(data) processed_data = process_data(data) json_data = convert_to_json(processed_data) save_report(json_data) send_report(json_data) end private def process_data(data) data.map { |item| Processor.process(item) } end def convert_to_json(data) data.to_json end def save_report(json_data) File.open('report.json', 'w') do |file| file.write(json_data) end end def send_report(json_data) Email.send(data: json_data) end end data = ["a", "b", "c", "d"] report = ReportGenerator.new report.generate_report(data)
- Avoid nested IFs (Hadouken IFs)
- Solution: Early Returns, Switch-Cases
- Before Avoid Hadouken IFs
def process_order(order) if order.valid? if order.in_stock? if order.payment_successful? puts "Order processed successfully!" else puts "Payment failed." end else puts "Item is out of stock." end else puts "Order is invalid." end end order = Order.new process_order(order)
- After Avoid Hadouken IFs
def process_order(order) return puts "Order is invalid." unless order.valid? return puts "Item is out of stock." unless order.in_stock? return puts "Payment failed." unless order.payment_successful? puts "Order processed successfully!" end order = Order.new process_order(order)
- Positive conditionals reduce mental strain and make it easier to reason about the code.
Note
"Whenever possible, conditionals should be expressed as positives."
β Robert C. Martin"
- Before Avoid Negative Conditionals
def check_access(user) if !user.admin? if !user.premium_member? puts "Access denied." else puts "Access granted." end else puts "Access granted." end end user = User.new check_access(user)
- After Avoid Negative Conditionals
def check_access(user) if user.admin? || user.premium_member? puts "Access granted." else puts "Access denied." end end user = User.new check_access(user)
- Extract complex conditionals into functions that convey the intent of the condition.
- Create names that reveal the intent of the conditional.
Note
"Boolean logic is hard enough to understand without having to see it in the context of an if or while statement. Extract functions that explain the intent of the conditional."
β Robert C. Martin"
- Before Encapsulate conditionals
def check_availability(user) if user.age > 18 && !user.has_children? && (user.premium_member? || user.has_coupon?) puts "User is available for the offer." else puts "User is not available for the offer." end end user = User.new check_availability(user)
- After Encapsulate conditionals
def check_availability(user) if available?(user) puts "User is available for the offer." else puts "User is not available for the offer." end end private def available?(user) return false if user.age <= 18 return false if user.has_children? return true if user.premium_member? return true if user.has_coupon? false end user = User.new check_availability(user)
- Avoid providing boolean arguments (
true/false) to functions or methods. - You could pass a string with a clearer name (for example).
Note
"Flag arguments are ugly. Passing a boolean into a function is a truly terrible practice."
β Robert C. Martin
- Before Avoid Flag Arguments
def greet(name, formal) if formal puts "Good evening, #{name}." else puts "Hi, #{name}!" end end greet("Alice", true) greet("Bob", false)
- After Avoid Flag Arguments
def greet(name, formality) case formality when "formal" puts "Good evening, #{name}." when "informal" puts "Hi, #{name}!" end end greet("Alice", "formal") greet("Bob", "informal")
- Prefer to avoid comments rather than to write them
- If a comment is truly necessary, explain the "why" not the "what".
Note
"Donβt use a comment when you can use a function or a variable."
β Robert C. Martin
Note
"When you feel the need to write a comment, first try to refactor the code so that any comment becomes superfluous."
β Martin Fowler
- Before Avoid Comments
class Calculator def calculate_area(radius) # Calculate the area of a circle area = Math::PI * radius ** 2 # Round the area to two decimal places area = area.round(2) area end end calculator = Calculator.new puts calculator.calculate_area(5) # Outputs: 78.54
- After Avoid Comments
class Calculator def calculate_area(radius) area = circle_area(radius) area = area.round(2) area end private def circle_area(radius) Math::PI * radius ** 2 end end calculator = Calculator.new puts calculator.calculate_area(5) # Outputs: 78.54
- Use descriptive variable names that reveal intent.
- Use pronounceable and easily searchable names.
- Use conventions (related to the language, the business, and the organization/team's communication).
Note
"You should name a variable using the same care with which you name a first-born child."
β Robert C. Martin
Note
"Any organization that designs a system will produce a design whose structure is a copy of the organizationβs communication structure."
β Robert C. Martin
- Before Good Nomenclatures
def fact(n) if n <= 1 1 else n * fact(n - 1) end end x = 5 y = fact(x) puts y # Outputs: 120
- After Good Nomenclatures
def factorial(number) if number <= 1 1 else number * factorial(number - 1) end end number = 5 result = factorial(number) puts result # Outputs: 120
- You should read your code from top to bottom.
- You should read your code without "jumping" over functions.
- Similar and dependent functions should be close vertically.
Note
"Programs must be written for people to read, and only incidentally for machines to execute."
β Hal Abelson and Jerry Sussman
Note
"Programming is the art of telling another human being what one wants the computer to do."
β Donald Knuth
- Before Use Vertical Formatting
class Calculator def add(a, b) a + b end def multiply(a, b) a * b end def calculate(a, b) sum = add(a, b) difference = subtract(a, b) product = multiply(a, b) quotient = divide(a, b) [sum, difference, product, quotient] end def divide(a, b) a / b end def subtract(a, b) a - b end end calculator = Calculator.new result = calculator.calculate(10, 5) puts result # 15, 5, 50, 2
- After Use Vertical Formatting
class Calculator def calculate(a, b) sum = add(a, b) difference = subtract(a, b) product = multiply(a, b) quotient = divide(a, b) [sum, difference, product, quotient] end def add(a, b) a + b end def subtract(a, b) a - b end def multiply(a, b) a * b end def divide(a, b) a / b end end calculator = Calculator.new result = calculator.calculate(10, 5) puts result # 15, 5, 50, 2
- Prefer to delete code rather than create code.
- Sometimes even new features can be created by deleting code.
Note
"One of my most productive days was when I threw away 1,000 lines of code."
β Ken Thompson
Note
"Code, like poetry, should be short and concise."
β Santosh Kalwar
Note
"Measuring programming progress by lines of code is like measuring aircraft building progress by weight."
β Bill Gates
- Before Delete Code > Create Code
class NotificationService def send_notification(user, message) if user.email_notifications_enabled send_email(user.email, message) else puts "Email notifications are disabled for #{user.name}." end if user.sms_notifications_enabled send_sms(user.phone_number, message) else puts "SMS notifications are disabled for #{user.name}." end end private def send_email(email, message) # Code to send email puts "Email sent to #{email}: #{message}" end def send_sms(phone_number, message) # Code to send SMS puts "SMS sent to #{phone_number}: #{message}" end end class User attr_accessor :name, :email, :phone_number, :email_notifications_enabled, :sms_notifications_enabled def initialize(name, email, phone_number) @name = name @email = email @phone_number = phone_number @email_notifications_enabled = false @sms_notifications_enabled = false end end # Usage user = User.new("Alice", "alice@example.com", "123-456-7890") user.email_notifications_enabled = true user.sms_notifications_enabled = true service = NotificationService.new service.send_notification(user, "Your order has been shipped.")
- After Delete Code > Create Code
class NotificationService def send_notification(user, message) send_email(user.email, message) send_sms(user.phone_number, message) end private def send_email(email, message) # Code to send email puts "Email sent to #{email}: #{message}" end def send_sms(phone_number, message) # Code to send SMS puts "SMS sent to #{phone_number}: #{message}" end end class User attr_accessor :name, :email, :phone_number def initialize(name, email, phone_number) @name = name @email = email @phone_number = phone_number end end # Usage user = User.new("Alice", "alice@example.com", "123-456-7890") service = NotificationService.new service.send_notification(user, "Your order has been shipped.")
- Always leave the campsite cleaner than you found it.
- Take the time to apply Clean Code principles to small parts of the code as you program.
- Over time, you will find the codebase much cleaner than when you found it!
Note
"Programming is a social activity."
β Robert C. Martin
Note
"If we all checked in our code a little cleaner than when we checked it out, the code simply could not rot."
β Robert C. Martin
- Before Boy Scout Rule
def print_user_info(user) puts "Name: " + user[:name] puts "Email: " + user[:email] if user[:age] != nil puts "Age: " + user[:age].to_s end end user = { name: "Alice", email: "alice@example.com", age: 30 } print_user_info(user)
- After Boy Scout Rule
def print_user_info(user) puts "Name: #{user[:name]}" puts "Email: #{user[:email]}" puts "Age: #{user[:age]}" if user[:age] end user = { name: "Alice", email: "alice@example.com", age: 30 } print_user_info(user)
You will find programmers out there who prefer fast and cryptic code over slow and readable code. But... was the code really slow? π€
Warning
"Programmers waste an enormous amount of time thinking or worrying about the speed of non-critical parts of their programs, and these attempts at efficiency have a strong negative impact when considering debugging and maintenance. We should forget about small efficiencies about 97% of the time: premature optimization is the root of all evil."
β Donald Knuth
Warning
"I asked a programmer why he wrote the code in one line instead of another approach that would better express the intent. His response was, 'This one is faster.' OK, it turns out that his code runs a picosecond (about 0.35 ps, to be precise) faster than what I proposed. And itβs triggered by a user's click on a button and executed only once. An absolutely insignificant time gain led to writing less readable code. Never do that: use the code that best expresses your intent for the next human who reads it, unless you really (really) need the execution time gain."
β Someone on Quora
Tip
"Make it work β Make it right (Clean Code) β Make it fast."
β Kent Beck
Clean code brings a series of advantages that positively impact individuals at all stages of system development.
Tip
"A good architecture makes the system easy to understand, easy to develop, easy to maintain, and easy to deploy. The ultimate goal is to minimize the lifetime cost of the system and maximize developer productivity."
β Robert C. Martin
- Robert C. Martin
- Martin Fowler
- Bill Gates
- Steve Jobs
- Don Wells
- Ray Ozzie
- Joel Spolsky
- Bjarne Stroustrup
- Ken Thompson
- Santosh Kalwar
- Donald Knuth
- Kent Beck
- Harold Abelson
- Jerry Sussman
- Douglas Crockford