Sunteți pe pagina 1din 26

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)

S-ar putea să vă placă și