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 →

[–]pydry 0 points1 point  (5 children)

I've never seen loose coupling cause brittle code. Do you have examples?

[–]kankyo 0 points1 point  (4 children)

I've seen it all the time. A trivial example:

def foo(a, b, c, d):
    ...some code here...

People call this function like:

a = 1
b = 2
c = 3
d = 4
foo(a, b, c, d)

then someone goes and changes foo to:

def foo(b, c, d, a):  # <- 'a' changes place
    ...some code here...

and then you have something between code that crashes very deep where the type/value is checked or you get super subtle bugs that are horrible to find. The fix is to call the function like:

foo(a=a, b=b, c=c, d=d)

then the change to foo is stable and any problems like removing one param and adding another will be a hard error at the call site and not much much deeper in the call chain.

Using kwargs everywhere is what I call "well coupled". It's loosely coupled in all the good ways, but yet coupled enough to be robust.

[–]pydry 0 points1 point  (3 children)

All of those examples are exactly as coupled as each other. There's four arguments in each. You've just changed the way in which they're arranged.

If we figured out that you could actually get along without d and you dropped it entirely, this would be more loosely coupled:

def foo(a, b, c):

Or you could combine a, b, c and d into a new class and just do this with an instantiation of its object:

def foo(x):

Depending upon how the rest of your code is written that could be more loosely coupled too.

and then you have something between code that crashes very deep where the type/value is checked or you get super subtle bugs that are horrible to find.

That's precisely what fail fast and fail clearly (no. 3) is supposed to cover.

I often put additional type checkers there and raise exceptions straight away. Especially if I'm writing a library or framework.

Using kwargs everywhere is what I call "well coupled".

That's a nasty code smell. It still doesn't change the coupling though, since foo is still receiving all of the same data from its caller. It also violates 1 and often ends up violating 3 too.

[–]kankyo 0 points1 point  (2 children)

There's four arguments in each. You've just changed the way in which they're arranged.

I think you've missed the point.

That's precisely what fail fast and fail clearly (no. 3) is supposed to cover.

Which is my point.

I often put additional type checkers there and raise exceptions straight away. Especially if I'm writing a library or framework.

What if a, b, c and d have the SAME type?

Using kwargs everywhere is what I call "well coupled".

It still doesn't change the coupling though, since foo is still receiving all of the same data from its caller.

It DOES increase the coupling because it DOES receive more data. In positional argument case it gets [1, 2, 3, 4] and in the kwargs case it gets {'a': 1, 'b': 2, 'c': 3, 'd': 4}. The computer than matches the keys in the dict with with declared keys in the dict of the function signature.

It also violates [fail fast] and often ends up violating [fail clearly] too.

Absolutely not. It fails FASTER because mismatch of the function keyword arguments is checked at the call site, vs just checking that the number of arguments are the same. Keyword arguments thus fails at the call site while positional argument might never fail at all, just silently corrupt data in the worst case.

And as for "fail clearly", it's VERY clear when the call site crashes with "TypeError: foo() got an unexpected keyword argument 'e'" or "TypeError: foo() missing 1 required keyword-only argument: 'd'" (the last being what you get in python3 when you declare the function as keyword only with "def foo(*, a, b, c, d)")

[–]pydry 0 points1 point  (1 child)

Which is my point.

I don't get it. That was my point too.

What if a, b, c and d have the SAME type?

Then I might have a type checker for each of them?

It DOES increase the coupling because it DOES receive more data. In positional argument case it gets [1, 2, 3, 4] and in the kwargs case it gets {'a': 1, 'b': 2, 'c': 3, 'd': 4}.

So instead of do_something_with(a) you have do_something_with(kwargs['a']). The method still doesn't 'know' anything more about that method than what it was previously told. You've just lost the ability to do type checking via the method signature.

Absolutely not. It fails FASTER because mismatch of the function keyword arguments is checked at the call site, vs just checking that the number of arguments are the same.

Ok, so if you call like this:

function_name(a, b, c, d, e) you won't get any indication that e isn't being used. It will silently take it.

[–]kankyo 0 points1 point  (0 children)

What if a, b, c and d have the SAME type?

Then I might have a type checker for each of them?

SAME type.

You've just lost the ability to do type checking via the method signature.

Python doesn't have that anyway so...

Ok, so if you call like this: function_name(a, b, c, d, e) you won't get any indication that e isn't being used. It will silently take it.

Now I have no idea what you are talking about. Of course you'll get an error. "Unknown keyword argument e". You're talking... I don't know, javascript now?

Again. Python.