Python 129 - Once More
I’m going to do the tiny object for the
nearest
code. I just want to see how it looks. Talk about gilding the lily.
We’ll TDD it, I think, copying the existing nearest
tests. New class name: Targeter.
It will just handle a single coordinate, either x or y: that will be enough to try out what I’m interested in. Here’s the first cut, passing its tests:
class Targeter:
def __init__(self, shooter_coordinate, target_coordinate, screen_size):
self.to_coordinate = target_coordinate
self.from_coordinate = shooter_coordinate
self.size = screen_size
self.half = self.size / 2
def nearest(self):
direct_distance = abs(self.to_coordinate - self.from_coordinate)
if direct_distance <= self.half:
return self.to_coordinate
elif self.from_coordinate >= self.half:
return self.to_coordinate + self.size
else:
return self.to_coordinate - self.size
What I wanted to try was some Extract Method action, hoping for more clarity. This may take a few tries on the names.
OK. Here’s the revised nearest
method from my new object:
def nearest(self):
if self.target_is_near:
return self.target_coordinate
elif self.we_are_past_center:
return self.target_on_high_side
else:
return self.target_on_low_side
Now it seems to me that that’s about as short and clear as it could be. Of course, we wonder a bit about how it got that way. But first, here’s the test, so you can see how we use this thing:
def test_can_choose_nearest_scalar_target(self):
target = 100
screen_size = 500
shooter = 200
assert Targeter(shooter, target, screen_size).nearest() == 100
shooter = 400
assert Targeter(shooter, target, screen_size).nearest() == 100 + 500
target = 400
shooter = 100
assert Targeter(shooter, target, screen_size).nearest() == 400 - 500
Same numbers, new notation.
How did we do it? Methods, converted to properties:
class Targeter:
def __init__(self, shooter_coordinate, target_coordinate, screen_size):
self.target_coordinate = target_coordinate
self.from_coordinate = shooter_coordinate
self.size = screen_size
self.half = self.size / 2
def nearest(self):
if self.target_is_near:
return self.target_coordinate
elif self.we_are_past_center:
return self.target_on_high_side
else:
return self.target_on_low_side
@property
def target_is_near(self):
return self.direct_distance <= self.half
@property
def direct_distance(self):
return abs(self.target_coordinate - self.from_coordinate)
@property
def we_are_past_center(self):
return self.from_coordinate >= self.half
@property
def target_on_high_side(self):
return self.target_coordinate + self.size
@property
def target_on_low_side(self):
return self.target_coordinate - self.size
Would I ever go that far? Well, in Python, I might not. In Smalltalk, I could imagine it, because Smalltalkers just love lots of tiny methods.
We might prefer a simpler calling sequence, like this:
def test_best_target(self):
target = 100
screen_size = 500
shooter = 200
assert Targeter.best_target(shooter, target, screen_size) == 100
shooter = 400
assert Targeter.best_target(shooter, target, screen_size) == 100 + 500
target = 400
shooter = 100
assert Targeter.best_target(shooter, target, screen_size) == 400 - 500
That’s easily done:
class Targeter:
@classmethod
def best_target(cls, shooter_coordinate, target_coordinate, screen_size):
return Targeter(shooter_coordinate, target_coordinate, screen_size).nearest()
We could fiddle with the names. But that would be silly, wouldn’t it?
All I really wanted to do was to get to this method, which I personally feel is very nice but almost certainly over-polished:
def nearest(self):
if self.target_is_near:
return self.target_coordinate
elif self.we_are_past_center:
return self.target_on_high_side
else:
return self.target_on_low_side
I don’t know whether to be proud or ashamed. Maybe both!
Will I plug it in? Very possibly. But, Ron! It’s certainly less efficient with all those method calls! Right, every two seconds or whatever, when we fire a targeted missile. Do I care about something that is microseconds slower every couple of seconds? Probably not.
See you next time!