- Pramod Kumar
We all started with it, if … else .. a beautiful branching tool. We would have used it count less time in our past. So did I. Everything was fine till it slowly started messing up my code.
Anyone who has built systems which is managed by more than ten member team, would understand my point. Okay enough said. Let me walk you through examples.
Lets assume we are in the process of building e-commerce application, specifically assigning our orders to logistic services. We have tied up with multiple logistic services who work in a range of postal codes. Example, DoneTodayLogistics delivers to areas whose postal codes are between 690034 to 695000.
if 690034 <= postal_code <= 695000: return "DoneTodayLogistics" elif ...: ...
This is a bare basic code that achieves our goal. In fact this looks clean now. But what if we add 10 more logistic services? You will have 10 if statements here. At this point, though it looks fine, something is burning inside, like a volcano.
Let us add one more requirement, we want to do a HTTP request when we have an order for them. Do you still just return string? I would not do that. I start abstracting some basic stuff and give some structure to Logistic Service. I will then have versions of LogisticService, like DoneTodayLogisticService. And I would move this if conditions inside the LogisticService. Here is the example
class LogisticService: def is_serviceable(self, postal_code) -> bool: mn, mx = self.get_postal_code_range() if mn <= postal_code <= mx: return True return False @abstractmethod def get_postal_code_range(self) -> Tuple(int, int): pass @abstractmethod def make_shipping_request(self, order): pass class DoneTodayLogisticService(LogisticService): def get_postal_code_range(self): return (690034, 695000) def make_shipping_request(self, order): # make http requests logistic_services = [DoneTodayLogisticService()] def assign_logistic_service(order): for logistic in logistic_services: if logistic.is_serviceable(order.postal_code): logistic.make_shipping_request() return logistic.__class__.__name__ view raw
We moved the if condition to else where and made it so simple! Now it is easy to understand the details or LogisticService because all the details related to it are at one place. So, we reduced 10 if blocks to one. This is what we have to keep in mind.
Okay, let me complicate it little bit more. Lets say there are some logistic services who don’t serve some set of postal codes in the range. They are red zones. They don’t serve there! As engineers, we have to some how support it. One obvious bad way of doing it is this
def assign_logistic_service(order): for logistic in logistic_services: if logistic.is_serviceable(order.postal_code): # if hack if logistic.__class__.__name__ == 'DoneTodayLogisticService': if order.postal_code in [690040, 690041]: continue logistic.make_shipping_request() return logistic.__class__.__name__
This for sure works. But is this code manageable? Few of my concerns are
- Now, some details of the logistic service are outside!
- What if we have exception list almost for all the logistic services?
So, better we treat it as a characteristic of a logistic service to have exception list of postal codes. Consider it in the structure we have for logistic service, like this
class LogisticService: def is_serviceable(self, postal_code) -> bool: mn, mx = self.get_postal_code_range() # use exception list here if mn <= postal_code <= mx and postal_code not in self.get_exception_postal_codes(): return True return False ... def get_exception_postal_codes(self) -> list: return  ... class DoneTodayLogisticService(LogisticService): ... def get_exception_postal_codes(self) -> list: return [690040, 690041] ...
Cool, we solved above two problems. I demonstrated this using classes, you can solve this by not using classes if you want. I don’t discuss that here because it is out of topic.
I personally deal with these hacks lot of times in my work. Small teams, startups are always (it’s bad though) want things to be done yesterday, developers don’t give much time to understand the design, and implement things properly. This is where all problem starts. One fine morning, you see one if statement which is lying somewhere completely inappropriate and lot of other services/systems/modules are already depending on it. First frustration hits you, because you cannot remove it easily, then more frustration because you cannot add more features either.
Though it is very simple to add an if statement, like a shortcut, it comes with a cost in long run. The more shortcuts you choose, the worst it would be. So be wise, take time now, design your system properly and avoid the pain later
This is my first post. If this post sounds silly, please excuse me. I just wanted to share my experience and thoughts :)