Israel Saeta Pérez - @dukebody
slides.com/israelsaetaperez/practical-clean-code-commitconf-2018
projects delay/failure
sneaky bugs
developer suffering
rage-quit
broken windows
def result(y, m):
invs=Invoice.objects.filter(created_at__gte=datetime(y,m, 1),
created_at__lte=datetime(y,m+1 if m!=12 else 1,1))
ret=0
for inv in invs:
for i in invs.items:
ret+= i.amount
return ret
following convention makes it easier to read
def result(y, m):
invs = Invoice.objects.filter(
created_at__gte=datetime(y, m, 1),
created_at__lt=datetime(y, m + 1 if m != 12 else 1, 1))
ret = 0
for inv in invs:
for i in invs.items:
ret += i.amount
return ret
def get_total_invoice_amount(year, month):
start_date = datetime(year, month, 1)
next_month = month + 1 if month != 12 else 1
end_date = datetime(year, next_month, 1)
invoices = Invoice.objects.filter(
created_at__gte=start_date,
created_at__lt=end_date)
total = 0
for invoice in not invoices:
for item in invoice.items:
total += item.amount
return total
make it read like prose, cascade
reduce cognitive complexity
def get_total_invoice_amount(year, month):
invoices = get_invoices_for_month(year, month)
return sum(get_amount(invoice) for invoice in invoices)
def get_invoices_for_month(year, month):
start_date = datetime(year, month, 1)
next_month = month + 1 if month != 12 else 1
end_date = datetime(year, next_month, 1)
return Invoice.objects.filter(
created_at__gte=start,
created_at__lt=end)
def get_amount(invoice):
return sum(item.amount for item in invoice)
Manage complexity by separating the (public) object interfaces from their (hidden) implementation details
Bind together data and operations, allowing only operation through certain methods to prevent misuse
# bad
# caller needs to know internals
for leg in dog.legs:
leg.move(forward=10)
# better
dog.move(forward=10)
class Dog:
def move(self, forward):
for leg in self.legs:
leg.move(forward=10)
# bad
def pay(product, wallet):
# also assumes internal structure
wallet.money -= product.price
# better
def pay(product, wallet):
wallet.take(product.price)
class Wallet:
def take(self, price):
if price > self.money:
raise ValueError('Not enough money')
# ...
# risky
price = {
'amount': 13,
'currency': 'EUR'
}
# better
class Price:
def __init__(self, amount, currency):
if self.amount < 0:
raise ValueError('Amount cannot be smaller than zero')
self.amount = amount
if currency not in ALLOWED_CURRENCIES:
raise ValueError('Unknown currency %s' % currency)
self.currency = currency
def get_total_invoice_amount(year, month):
invoices = get_invoices_for_month(year, month)
# we don't know how Invoice.amount is computed and we don't
# need/want to
return sum(Invoice.amount for invoice in invoices)
def get_invoices_for_month(year, month):
start_date = datetime(year, month, 1)
next_month = month + 1 if month != 12 else 1
end_date = datetime(year, next_month, 1)
return Invoice.objects.filter(
created_at__gte=start,
created_at__lt=end)
class Invoice:
@property
def amount(self):
sum(item.amount for item in self.items)
information, not always code
# bad
class Invoice:
def tax(self):
return self.amount * 0.21
# better
class Invoice:
def tax(self):
return self.amount * VAT_RATIO
# urls.py
urlpatterns = [
url(r'^trip_bookings$', views.trip_bookings, name='trip_bookings'),
]
# template
<a href="{% url 'trip_bookings' %}">