Clean Code
Keeping your code readable
By: Arnoldo Rodriguez
@arnoldocolin
Giving Meaningful names
Intention revealing
Pronounceable
Distinctable
Class names should be nouns / noun verbs
Method names should be verbs / verb
phrase
One Word per concept
Names: Intention Revealing & Pronounceable
edt
end_date_time
sdt
start_date_time
nme
name
fname
stotal
VS
full_name
subtotal
pgup
page_up
lng
longitud
Names: Distinctable, never duplicate...
info
description
destroy
delete
name
fullname
place
house
process
VS
location
home
execute
Names: Class names should be a...
Noun
User
Form
Friend
Noun phrase
Signup
DiscountCalculator
TripFinder
Names: Method names should be a...
Verb
invite
authorize
validate
Verb Phrase
find_location
has_friendship_with
send_email
Functions should...
be small (do 1 thing, 1 level of abstraction)
never use switch statements
1, 2, 3 arguments, no more!
Do something or answer something
have no side effects
Functions: Do 1 thing & 1 Level of Abstraction
class Order
# Wrong: calculating several things
def total
subtotal = items.sum(:price)
discount = items.sum(:discount)
subtotal - discount
end
end
class Order
# Good. One level of abstraction, One thing
# Remember to declare utility functions just
# below where they are being called
def total
subtotal - discount
end
def subtotal
items.sum(:price)
end
def discount
items.sum(:discount)
end
end
Not OK
Ok!
Functions: Never use switch statements
class Payment
attr_reader payment_method
class Payment
attr_reader payment_method
def process
case payment_method.class
when "CreditCart"
when "Deposit"
when "Cash"
end
end
# A switch statement hides an
# opportunity to use
# polymorphism or duck typing
def process
payment_method.purchase(self)
end
end
Not OK
Ok!
Functions: Zero arguments is the winner!
No arguments to remember
Use instance variables, but remember the cohesion
principle:
All methods (in a class) should use most of the
instance variables in that class
Functions: One argument, Ask? or Transform not both
class User
# This is asking something
def has_friendship_with(another_user)
Friendship.exists?(self, another_user)
end
# This is transforming something
def change_password(new_password)
self.password = new_password
end
end
Functions: Two arguments sucks
Avoid or convert to one argument using
instance variables.
Give functions better names.
# Hard to remember gotta google it bitch!
asserts_equal(expected, actual)
# Easy to remember no need for the api
asserts_expected_to_eq_actual(expected, actual)
Functions: Three arguments, avoid at all costs
If you cant then use:
argument objects
argument lists
# send options hash for a list html options
def text_field_tag(name, value, html_options={})
end
# convert x, y coordinates into a center object
def make_circle(center, radius)
end
Functions: Make sure they have no side effects
# The side effect is sending an email notification
def create_account(attributes={})
account = Account.create(attributes)
send_notification_email(account) unless account.new_record?
end
# We can give the function a better name
# though violates the do 1 thing
# the best solution is to abstract it to a new object signup
def create_account_and_notify(attributes={})
end
Comments are...
Shitz
A pain in the ass to maintain
To be avoided if we follow good design
practices
Exceptions: return exceptions instead of error codes
# Using an error code
def withdraw(amount)
if amount > balance
return -1
else
balance -= amount
end
end
Not OK
# Using an exception, easier coz it give us
#context
def withdraw(amount)
raise BalanceException if amount > balance
balance -= amount
end
Ok!
Unit tests should follow...
3 Laws of TDD
One assert per test
F.I.R.S.T.
Unit Tests: 3 Laws of TDD
First: You may not write production code until you have
written a failing unit test.
Second: You may not write more of a unit test than is
sufficient to fail
Third: You may not write more production code than is
sufficient to pass the currently failing test.
Unit Tests: One assert per test
Use the Given, When, Then to test one
concept per test.
Unit Tests: F.I.R.S.T.
Fast: Tests should be fast.
Independent: Tests should not depend on each other.
Repeatable: Tests should be repeatable in any
environment.
Self-Validating: Test should have a boolean output.
Timely: Unit Tests should be write just before
production code.
Classes should...
Keep variables & utility functions private
Follow the Single Responsibility Principle
Follow the Open-Closed principle
Follow the Dependency inversion Principle
Follow the law of Demeter
Classes: Single Responsibility Principle
# Several responsibilities
class User
attr_accessor first_name, last_name, email
def full_name
"#{first_name} #{last_name}"
end
def send_confirmation_email
end
def recover_password
end
end
# By dividing responsibilities
# we ended up with more objects
class User
attr_accessor first_name, last_name, email
def full_name
"#{first_name} #{last_name}"
end
end
class PasswordRetriever
def retrieve
end
end
class Signup
def sign_up
end
private
def send_confirmation_email
end
end
Not OK
Ok!
Classes: Open-Close Principle
# Everytime we want a new SQL method
# we will have to open and change the class
# clearly violates Open-Close Principle
class SQL
def insert
# codez, codez...
end
# Using inheritance we follow the Open-Close #
Principle
# A class must be Open for extension but
#Close for modification
class SQL
# superclass codez goes here...
end
def select
# codez, codez...
end
end
class Insert < SQL
# specialization codez goes here...
end
class Select < SQL
# specialization codez goes here...
end
Not OK
Ok!
Classes: Dependency Inversion
class Report
# Class dependent, what if we need another format?
def export_pdf
PDFExporter.export(self)
end
# Inverting the dependency, we can use any format now
def export(format = PDFExporter)
format.export(self)
end
end
# Endless possibilities
report = Report.new
report.export(PDFExporter)
report.export(XLSExporter)
report.export(NumbersExporter)
report.export(HTMLExporter)
Classes: Law of Demeter
Method F of class C should only call methods of these
(only use one dot):
C
An object created by F
An object passed as an argument to F
An object held in an instance variable of C
class jockey
# we don't need to tell the paws to move
def run_horse
horse.paws.move(speed: fast)
end
end
Not OK
class jockey
# use only one dot!
def run_horse
horse.run
end
end
Ok!
A simple design is one that...
Runs all tests
Has no duplication
Expresses intent
Minimizes the number of classes
Refactor (make it work, make it right)