9 steps to better OO code
Paweł Lewtak
Senior Developer at Xstream
@pawel_lewtak
Learn how to make our code more:
"Calisthenics are exercises consisting of a variety of gross motor movements; often rhythmical and generally without equipment or apparatus."
Wikipedia
Author unknown
Art of Readable Code by Dustin Boswell, Trevor Foucher
class Board(object):
def __init__(self, data):
# Level 0
self.buf = ""
for i in range(10):
# Level 1
for j in range(10):
# Level 2
self.buf += data[i][j]
class Board(object):
def __init__(self, data):
self.buf = ""
self.collect_rows(data)
def collect_rows(self, data):
for i in range(10):
self.collect_row(data[i])
def collect_row(self, row):
for j in range(10):
self.buf += row[j]
class UserService(object):
def register(self, username, email, promo_code = False):
user = self.create_user(username)
if email:
send_email(user, email)
if promo_code:
send_promo_code(user, promo_code)
products = self.get_products_by_user(...)
if products is None:
products = self.get_products_by_media(...)
if products is None:
products = self.get_products_by_domain(...)
if products is None:
products = self.get_any_products(...):
if products is None:
raise Exception('Access denied')
else:
...
else:
...
else:
...
else:
...
products = self.get_products_by_user(...)
if products is None:
products = self.get_products_by_media(...)
if products is None:
products = self.get_products_by_domain(...)
if products is None:
products = self.get_any_products(...):
if products is None:
raise Exception('Access denied')
# else...
class Command(object):
next_command = None
def add(self, next_command):
if self.next_command is None:
self.next_command = next_command
else:
self.next_command.add(next_command)
def _process(self, *args, **kwargs):
""" computations """
pass
def process(self, *args, **kwargs):
result = self._process(*args, **kwargs)
if result is None:
if self.next_command is not None:
return self.next_command.process(*args, **kwargs)
else:
return result
return None
class GetProductsForUser(Command): pass
class GetProductsByMedia(Command): pass
class GetProductsByDomain(Command): pass
class GetAnyProducts(Command): pass
commands = GetProductsForUser()
commands.add(GetProductsByMedia)
commands.add(GetProductsByDomain)
commands.add(GetAnyProducts)
products = commands.process(...)
def login (self, request):
if request.user.is_authenticated():
return redirect("homepage")
else:
messages.add_message(request,
messages.INFO,
'Bad credentials')
return redirect("login")
def login (self, request):
if request.user.is_authenticated():
return redirect("homepage")
messages.add_message(request,
messages.INFO,
'Bad credentials')
return redirect("login")
def function(param):
if param is not None:
value = param
else:
value = "default"
return value
def function(param):
value = "default"
if param is not None:
value = param
return value
def function(var_a, var_b, var_c, var_d):
if var_a:
if var_b:
# some code
else:
# some code
elif var_b and var_c:
if not var_d:
# some code
else:
# some code
elif var_b and not var_c:
# some code
else:
# some code
https://www.quora.com/Is-it-true-that-a-good-programmer-uses-fewer-if-conditions-than-an-amateur
class Validator(object):
def check_date(self, year, month, day):
pass
# 10th of December or 12th of October?
validator = Validator()
validator.check_date(2016, 10, 12)
class Validator(object):
def check_date(self, year: Year, month: Month, day: Day) -> bool:
pass
# Function call leaves no doubt.
validator.check_date(Year(2016), Month(10), Day(12))
def calculate_distance(source_x, source_y, target_x, target_y):
pass
calculate_distance(1, 2, 3, 4)
from collections import namedtuple
class Point2D(namedtuple("Point2D", "x y")):
pass
def calculate_distance(source_point, target_point):
pass
calculate_distance(Point2D(1, 2), Point2D(3, 4))
class Poem(object):
def __init__(self, content):
self.content = content
def indent(self, spaces):
self.content = " " * spaces + self.content
return self
def suffix(self, content):
self.content = self.content + " - " + content
return self
Poem("Road Not Travelled").indent(4)\
.suffix("Robert Frost")\
.content
class CartService(object):
def get_token(self):
token = self.get_service('auth')\
.auth_user('user', 'password')\
.get_result()\
.get_token()
return token
# 1. What if None is returned instead of object?
# 2. How about exceptions handling?
class Field(object):
def __init__(self):
self.current = Piece()
class Piece(object):
def __init__(self):
self.representation = " "
class Board(object):
def board_representation(self, board):
buf = ''
for field in board:
buf += field.current.representation
return buf
class Field(object):
def __init__(self):
self.current = Piece()
def add_to(self, buffer):
return self.current.add_to(buffer)
class Piece(object):
def __init__(self):
self.representation = " "
def add_to(self, buffer):
return buffer + self.representation
class Board(object):
def board_representation(self, board):
buf = ''
for field in board:
buf = field.add_to(buf)
return buf
def register_user_send_welcome_email_and_add_to_default_groups():
pass
# vs
def handle_user_registration():
user = create_user()
send_welcome_email(user)
add_to_default_groups()
acc = 0
// accumulator? accuracy?
pos = 100
// position? point of sale? positive?
auth = None
// authentication? authorization? both?
class Order(object):
def ship_order(self):
pass
order = Order()
order.ship_order()
// vs
class Order(object):
def ship(self):
pass
order = Order()
order.ship()
class CartService(object):
def __init__(self):
self.logger = Logger()
self.cart = CartCollection()
self.translationService = TranslationService()
self.auth_service = AuthService()
self.user_service = UserService()
class CartService(object):
def __init__(self):
self.logger = Logger()
self.cart = CartCollection()
class Game(object):
def __init__(self):
self.score = 0
def set_score(self, score):
self.score = score
def get_score(self):
return self.score
# Usage
ENEMY_DESTROYED_SCORE = 10
game = Game()
game.set_score(game.get_score() + ENEMY_DESTROYED_SCORE)
class Game(object):
def __init__(self):
self.score = 0
def add_score(self, score):
self.score += score
# Usage
ENEMY_DESTROYED_SCORE = 10
game = Game()
game.add_score(ENEMY_DESTROYED_SCORE)
@pawel_lewtak