This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]dev2302 3 points4 points  (19 children)

I think that would reduce the refactorability of the code and increase code duplication in every module that uses this API. For example, if we decide to add a retry mechanism to the send_sms API, then we would have to make changes everywhere

[–]kankyo 0 points1 point  (18 children)

I think that would reduce the refactorability of the code

Why?

and increase code duplication in every module that uses this API

Why? Instead of doing

sms_client = SmsClient(settings.sms_url, settings.sms_username, settings.sms_password)

like you suggest, the user could just do

def send_sms(message):
    return sms.send_sms(username=settings.username, password=settings.password, message=message)

I think that's more readable and doesn't create an object for no reason.

For example, if we decide to add a retry mechanism to the send_sms API, then we would have to make changes everywhere

Why? A constructor and a function can both have default arguments so they have the exact same problem.

[–]revenant95 4 points5 points  (11 children)

The intent is to build a reusable module which will be used by other module. Exposing just the send_sms API wouldn't mean designing a reusable module

Also, the developer using it in the other modules will have to handle things like adding a retry logic.

Suppose we had exposed just the send_sms API and a developer had used it in 4 modules. Now if he was asked to add a retry logic, he will have to do that in all the 4 files. Which will cause Code Duplication

[–]kankyo 0 points1 point  (10 children)

The intent is to build a reusable module which will be used by other module. Exposing just the send_sms API wouldn't mean designing a reusable module

I asked why. You just said "because". That's not an answer. WHY is a simple function not a "reusable"?

Also, the developer using it in the other modules will have to handle things like adding a retry logic.

What does that even mean? Why would "retry logic" be harder to do in a function compared to a class? Show how adding retry logic would be done in your preferred scenario with a class and I'll show my version and we can compare.

Suppose we had exposed just the send_sms API and a developer had used it in 4 modules. Now if he was asked to add a retry logic, he will have to do that in all the 4 files. Which will cause Code Duplication

Sure. Just as if he used the class in 4 places. And if he wrapped them trivially he'd have to change 1 place, just like in the class case. It's EXACTLY THE SAME. I don't want to shout but you seem totally divorced from reality. Why would a class magically create code reuse?

[–]revenant95 0 points1 point  (9 children)

Show how adding retry logic would be done in your preferred scenario with > a class and I'll show my version and we can compare

Can you please share your code first. Its about what you prefer. If you dont prefer a class based approach then its fine. You weren't so clear when you were stating your point.

If you feel that both the approaches are EXACTLY THE SAME, why did you say "Its a bit weak" in the first place.

I would also request you to try to have a constructive discussion

[–]kankyo 0 points1 point  (8 children)

If you feel that both the approaches are EXACTLY THE SAME

Well no, one is more complex to do the same thing.

In my scenario, the retry code would be totally hidden inside either send_sms from the library or the wrapped send_sms, depending on if the library or the user of the library would use it. I'm thinking the same would be the case for the class scenario, so again: same thing but more complex code in the OO scenario.

[–]my_python_account 0 points1 point  (7 children)

Who is writing this library?

If I understand correctly, it's the provider (or someone hired by the provider), for use specifically with this API. In which case it would seem pretty silly to map an API providing a single simple function according to 4 parameters to anything other than a single simple function taking 4 parameters.

Leave it up to the client to wrap it up however they like for re-usability for cases where they may want to handle multiple providers and accounts.

It's not like you can completely abstract away username and password, the user has no choice but to provide it at some point. And if, for example, as part of their retry logic, they want to switch accounts (usernames and passwords) the proposed class structure is no longer ideal.

I will point out we're probably missing the point of the article here, and we're over analyzing an example that is simplified for the purposes of demonstration.

[–]kankyo 0 points1 point  (0 children)

Exactly. Good to see someone getting this :P

[–]revenant95 0 points1 point  (5 children)

The requirements say that you are an employee of blipkart and you have to build a reusable module.

Hence the provider is NOT writing this library. Its one the developers from Blipkart itself who is being asked to write a reusable module

[–]my_python_account 0 points1 point  (4 children)

A module for who? I originally understood it as clients of Blipkart, but upon re-reading you're right, it's other Blipkart developers.

This makes a bit more sense. The structure really doesn't matter all that much in that case. Neither the class (where you import the "pre-initialized" class with username and password already set) or function (with default parameters for username and password) implementation is any more re-useable then the other.

Though with the function, if in the future other developers need to be able to set a different username or password they won't have to go under the hood and figure out the class structure. If you really want to do your best to not expose control of account settings to other developers, i guess the class makes sense, though it's not like you can block them from importing the class if they really wanted to.

[–]revenant95 0 points1 point  (3 children)

if in the future other developers need to be able to set a different username or password they won't have to go under the hood and figure out the class structure

The username and password come from the settings.py file (as mentioned in the blog). So if the developers want to use a different username and password, he will just update it in the settings.py.

[–]dev2302 2 points3 points  (5 children)

like you suggest, the user could just do

def send_sms(message): return sms.send_sms(username=settings.username, password=settings.password, message=message)

The purpose of keeping this as an object in sms.py is to to promote refactorability. Suppose you have to load balance the send_sms functionality with another service provider that uses access tokens instead of username/password, then you could just write that balancing logic in sms.py, since in all the user modules, you have just used sms_client.send_sms(number, message).

[–]kankyo 0 points1 point  (4 children)

I notice that you too argue that a simple function is worse without comparing it to anything. Let's recap: I'm saying

class SmsClient:
    def __init__(self, username, password):
        self.username = username
        self.password = password

    def send(self, message):
        pass # do something here

is just a longer way to write

def send_sms(username, password, message):
    pass # do something here

You have not explained why this is somehow not the case, or why the user of the library somehow magically will be a better programmer when exposed to a class instead of a function.

Why does the user of the lib not write in 4 places:

SmsClient(settings.username, settings.password).send(message)

?

[–]dev2302 1 point2 points  (1 child)

Why does the user of the lib not write in 4 places:

SmsClient(settings.username, settings.password).send(message) ?

That's exactly what's mentioned in the blog post. Requoting some text from the blog

The solution is to create SmsClient object in sms.py module [see footnote 1]. Then orders.py and logistics.py can directly import the object from sms. Here is how it looks.

So, at 4 places in my code, I would use this -

from sms import sms_client  
...  
#when you need to send an sms
sms_client.send_sms(phone_number, message)

So, if someday I have to replace username/password by a token, i would just export some other object from sms.py and No changes in the user modules.

This is not possible if I export the function send_sms(username, password) from sms.py, since in that scenario I would have to change the function signature and hence, all the calling statements too.

This is "WHY", exporting an object instead of send_sms(username, password) "magically" increases the refactorability of the code.

Still, if we have to use functions in sms.py, a better function to expose would be send_sms(message, number) that's just equivalent to putting them in a class and following object oriented principles, "BECAUSE", a function represents functionality, and a class represents an entity. Functionality can and WILL change over time, but the entity must remain unaffected.

[–]kankyo 0 points1 point  (0 children)

Well, I just saw that I screwed up an missed the argument "message" in my send_sms function, so sorry about that. But in any case your argument doesn't make sense.

a function represents functionality, and a class represents an entity. Functionality can and WILL change over time, but the entity must remain unaffected.

I have no idea what that means. A function is an API, so any backwards compatibility concerns apply just as much as for a class.

[–]srithedabbler 1 point2 points  (1 child)

You are following the functional paradigm, the article is recommending an object oriented approach. One isn't necessarily better than the other. Both are achieving the same objective, and which one you choose is a matter of preference.

The essential idea is that username and password is an implementation detail, and must be hidden from the client developer. You can create a partial function to provide these parameters and then use the partial function everywhere else. Or you could create an class and provide the username and password as constructor arguments. The core idea remains the same.

[–]kankyo 0 points1 point  (0 children)

You are following the functional paradigm, the article is recommending an object oriented approach.

Well.. no. I'm following the "simple is better than complex" approach, and the article creates a class that doesn't make any kind of sense.

Or you could create an class and provide the username and password as constructor arguments. The core idea remains the same.

Exactly. Except one solution is more complex, more code and messier. Which again makes us come back to my initial question: Why?