Any fool can write code that a
computer can understand. Good
programmers write code that
humans can understand.
!
Martin Fowler [@martinfowler]
Slide 3
Slide 3 text
What makes code
difficult to work with?
Slide 4
Slide 4 text
YMMV
Slide 5
Slide 5 text
you’re never going to
get it perfect first time
Slide 6
Slide 6 text
Defer concrete decisions as late as
possible - you'll never again know less
about the problem than you do right
now and the correct abstraction will
become clearer over time.
!
Andy Appleton [@appltn]
Slide 7
Slide 7 text
writing good code
improving existing code
Slide 8
Slide 8 text
Writing Good Code:
Tests
Slide 9
Slide 9 text
drive your API design
Slide 10
Slide 10 text
difficult to test
==
difficult to use
Slide 11
Slide 11 text
confidence when refactoring
(but we’ll come back to this
later)
Slide 12
Slide 12 text
Writing Good Code:
Naming Conventions
Slide 13
Slide 13 text
There are only two hard things
in Computer Science: cache
invalidation and naming things.
!
Phil Karlton
Slide 14
Slide 14 text
There are only two hard things in
Computer Science: cache
invalidation, naming things and
off by one errors.
!
???
Slide 15
Slide 15 text
prefer verbosity
Slide 16
Slide 16 text
be consistent
use the same nouns and
verbs
Slide 17
Slide 17 text
Writing Good Code:
Coding Standards
Slide 18
Slide 18 text
editorconfig.org
Slide 19
Slide 19 text
actual standard is
irrelevant
pick one and stick to it
Slide 20
Slide 20 text
Writing Good Code:
Single Responsibility
Slide 21
Slide 21 text
do one thing and do it
well
Slide 22
Slide 22 text
Not one thing well
class EmailSender
def initialize(csv)
!
def parse_csv_for_emails
!
def send_email
end
Slide 23
Slide 23 text
Better
class EmailSender
def initialize(csv)
def parse_csv_for_emails
Parser.new(csv).emails
def send_email
end
!
class Parser
def initialize(csv)
def emails
end
Slide 24
Slide 24 text
Writing Good Code:
Decoupled Components
Slide 25
Slide 25 text
class EmailSender
def initialize(csv)
def parse_csv_for_emails
Parser.new(csv).emails
def send_email
end
!
class Parser
def initialize(csv)
def emails
end
This knowledge isn’t needed here
Slide 26
Slide 26 text
class EmailSender
def initialize(emails)
def send_email
end
!
class Parser
def initialize(csv)
def emails
end
!
emails = Parser.new(csv).emails
EmailSender.new(emails).send_email
knows how to send an email to an array of emails
knows how to parse a CSV and get the email addresses
Slide 27
Slide 27 text
Writing Good Code:
Separate Aggressively
Slide 28
Slide 28 text
don’t be afraid to create
classes / objects
Slide 29
Slide 29 text
easier to merge two tiny
classes than split up one
massive one
Slide 30
Slide 30 text
Refactoring
Slide 31
Slide 31 text
No content
Slide 32
Slide 32 text
Refactoring: alter code
without changing
behaviour
Slide 33
Slide 33 text
you have to have tests!
Slide 34
Slide 34 text
Code Smells
Slide 35
Slide 35 text
Code Smell: some code
which may indicate a
problem
Slide 36
Slide 36 text
You don’t fix code smells. You
look at them and see if they
indicate a problem you can fix.
!
Joe Ferris [@joe_ferris]
Slide 37
Slide 37 text
class SomeMapThing
def initialize(x, y)
end
!
class LatLong
def self.get_lat_long(x, y)
end
!
class User
def coords
[x, y]
end
Slide 38
Slide 38 text
data clumps
continually passing round
two objects as arguments
Slide 39
Slide 39 text
class SomeMapThing
def initialize(coords)
!
class LatLong
def self.get_lat_long(coords)
!
class User
def coords
!
class Coords
def x
def y
!
coords = Coords.new(2, 3)
LatLong.get_lat_long(coords)
Slide 40
Slide 40 text
if two arguments are tightly
related, encapsulate that
knowledge
Slide 41
Slide 41 text
class GraphDrawer
def draw
width = 165 + bar_width
height = 170.2 + bar_height
…
end
Slide 42
Slide 42 text
why 165?
why 170.2?
This is very common in JavaScript / jQuery code, and CSS too.
Slide 43
Slide 43 text
class GraphDrawer
GRAPH_HEIGHT_PADDING = 170.2
GRAPH_WIDTH_PADDING = 165
def draw
width = GRAPH_WIDTH_PADDING * bar_width
height=GRAPH_HEIGHT_PADDING + bar_height
end
Slide 44
Slide 44 text
no knowledge should
be left in your head
Slide 45
Slide 45 text
<% if user != nil %>
<%= user.welcome_message %>
<% else %>
Please sign in
<% end %>
Slide 46
Slide 46 text
checking for nil /
undefined
Slide 47
Slide 47 text
what does nil mean?
no first name?
system error?
no name yet?
no user logged in?
Slide 48
Slide 48 text
implicit knowledge in a
codebase is bad
Slide 49
Slide 49 text
class NullUser
def welcome_message
“Please sign in”
end
!
user = current_user || NullUser.new
<%= user.welcome_message %>
Slide 50
Slide 50 text
don’t check for nil
Slide 51
Slide 51 text
def check_for_overheating(system_monitor)
if system_monitor.temperature > 100
system_monitor.sound_alarms
end
end
Slide 52
Slide 52 text
def check_for_overheating(system_monitor)
if system_monitor.temperature > 100
system_monitor.sound_alarms
end
end
def save(should_run_validations=true)
if should_run_validations
validate
store
else
store
!
# in our app, everywhere
user.save
post.save(false)
Slide 58
Slide 58 text
control coupling
Slide 59
Slide 59 text
if I update the save method
every usage of the method
has to change
Slide 60
Slide 60 text
(this actually happened
in Rails)
Slide 61
Slide 61 text
better solution would be
to split the methods up
Slide 62
Slide 62 text
you have to have some
coupling, else nothing
could talk to anything
Slide 63
Slide 63 text
“If I change this method,
how many changes do I
have to make else where?”
Slide 64
Slide 64 text
Publish and Subscribe
Slide 65
Slide 65 text
class ModuleA
def init(b)
def a_thing
do_a_thing
b.do_thing
!
!
class ModuleB
def do_thing
# has to happen after ModuleA#a_thing
Slide 66
Slide 66 text
Module A knows too
much
Slide 67
Slide 67 text
class ModuleA
def init(event)
def a_thing
do_a_thing
event.publish(‘a_thing_complete’)
!
!
class ModuleB
def init(event)
event.subscribe(‘a_thing_complete’) {
do_thing
}
ModuleA doesn’t know (or care) what modules listen to a_thing_complete
Slide 68
Slide 68 text
Now no module knows
about the other
Slide 69
Slide 69 text
class ModuleA
def init(event)
def a_thing
do_a_thing
event.publish(‘a_thing_complete’)
!
class ModuleB
…
class ModuleC
def init(event)
event.subscribe(‘a_thing_complete’) {
do_thing
}
Slide 70
Slide 70 text
class User
def posts_by_user
blog.posts.where(user_id: id)
end
Slide 71
Slide 71 text
The Law of Demeter
Slide 72
Slide 72 text
It’s an actual law!
Slide 73
Slide 73 text
A method of an object should
only invoke only the methods of
the following kinds of objects:
!
1. itself
2. its parameters
3. any objects it creates
4. its direct component objects
Slide 74
Slide 74 text
class User
def posts_by_user
blog.posts.where(user_id: id)
end
multiple dots
Slide 75
Slide 75 text
class User
def posts_by_user
blog.posts_by_user(id)
end
!
class Blog
def posts_by_user(id)
posts.where(user_id: id)
end
Slide 76
Slide 76 text
avoid duplication of
knowledge across your
system
Slide 77
Slide 77 text
OK, so it’s not an actual
law
Slide 78
Slide 78 text
Approaches to
maintaining code
Slide 79
Slide 79 text
Every time you work with
some code, leave it a tiny bit
better than when you left it
Slide 80
Slide 80 text
don’t ignore a problem or
leave #FIXME comments
Slide 81
Slide 81 text
#todo: this entire
class is screwed, fix
it
Slide 82
Slide 82 text
if you need to make the
change, but the change is
difficult
Slide 83
Slide 83 text
first refactor to make the
change easy
then make the change!