Often times, when writing Python, I run into a situation that requires me to write a simple validation function.
def check_validity(item): return item.value_to_check > 0
This function is easy to test, and it’s clear what it’s supposed to be doing. But as so often happens, I may need to validate more than the single attribute I originally checked.
def check_validity(item): valid = False if item.value_to_check > 0: valid = True if valid and item.another_value_to_check < 0: valid = False return valid
Now this function has become quite a bit more complex. However, since its only responsibility is to validate that
item is still valid and that I can still test it, it's really easy to talk myself into accepting the code as it is.
def check_validity(item): valid = False if item.value_to_check > 0: valid = True if valid and item.another_value_to_check < 0: valid = False if valid: for value in item.list_to_check: if value < 0: valid = False break return valid
Despite the fact that I can still test this code, there are a couple of problems that I can no longer overlook. Namely, I've introduced state and nested
for statements. But what do I do about it?
def check_validity(item): def value_is_valid(value): return value > 0 def check_list(value_list): for value in item.list_to_check: if value < 0: valid = False break return value_is_valid(item.value_to_check) and\ value_is_valid(item.another_value_to_check) and\ check_list(item.list_to_check)
This is an improvement, since I've compartmentalized the outer
if statements into functions of their own, as well as removed the
valid variable. The problem, however, is that despite the fact that I can test
check_validity, I can't test either of the nested functions. If one of those functions grows in complexity, there's an excellent chance that a bug might slip my notice.
Thankfully, I can turn to the Command Pattern.
class CheckValidityCommand(object): def _value_is_valid(self, value): return value > 0 def _check_list(self, value_list): for value in item.list_to_check: if value < 0: valid = False break def __call__(self, item): return self._value_is_valid(item.value_to_check) and\ self._value_is_valid(item.another_value_to_check) and\ self._check_list(item.list_to_check)
By refactoring the
check_validity function into a class, we now have direct access to those nested functions that were previously hidden from us. Furthermore, consuming the class is really simple.
check_validity = CheckValidityCommand() assert(check_validity(item_that_passes) == True) assert(check_validity(item_that_fails) == False)