[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

PyWart: More surpises via "implict conversion to boolean" (and other steaming piles!)

On Tue, 11 Feb 2014 07:36:34 -0800, Travis Griggs wrote:

> On Feb 10, 2014, at 10:30 PM, Steven D'Aprano <steve at pearwood.info>
> wrote:
>>>    1. Parenthesis should not be required for parameter- less
>>>    functions.
>> Of course they should. Firstly, parameter-less functions are a code-
>> smell, and ought to be discouraged. Secondly, even if you have a good
>> reason for using one -- for example, random.random -- then the
>> difference between referring to the object and calling the object
>> should be clear.
> Interesting. Can you clarify or provide some links to the
> "parameter-less functions are a code-smell? bit?

Functions map a value to another value. They can be one-to-one, or many-
to-one. (Mathematically, they cannot be one-to-many or many-to-many, 
that's called a relation.) What about zero-to-one?

If the function always returns the same result, e.g.:

def spam():
    return "spam spam spam"

why are you using a function? Just create a constant and use that. 
Calling a function which always returns the same value is a code smell. 
That's not to say it is always wrong, but it smells a bit off.

How about zero-to-many functions? E.g. you have a situation where calling 
function() twice might return different values. Okay, here's an example:

=> returns 42
=> returns 23

Hmmm. There's a bug in give_me_an_even_number(). How do I reproduce that 
bug? What arguments do I pass? Oh, the same no-arguments as for the 
working call.

Clearly, the function must have *hidden state*. Hidden state (e.g. a 
global variable) makes it hard to reason about the function call, since 
you don't know what the hidden state is. So that's also a bit smelly.

Hidden state is generally bad, because it makes it hard to reason about 
the function call, hard to reproduce results, hard to debug, hard to 
test. Think about the difference in difficulty in confirming that 
math.sin() of some value x returns the value 0.5, and confirming that 
random.random() of some hidden state returns a specific value:

py> assert math.sin(0.5235987755982989) == 0.5


py> state = random.getstate()
py> random.seed(12345)
py> assert random.random() == 0.41661987254534116
py> random.setstate(state)

> OTOH, I?m not sure I?ve heard the parameters-less functions are a code
> one? Is it just loose functions that you?re referring to? As opposed to
> methods (which are just bound functions)? I could maybe accept that. But
> methods with fewer arguments, and even none, are a desirable thing.

Methods that appear to take zero arguments actually take one argument, it 
is just that it is written in a different place:

"some string".upper()

is merely different syntax for:

upper("some string")

with the bonus that str.upper and MyClass.upper live in different 
namespaces and so can do different things. So I have no problem with zero-
argument methods, or functions with default values.

Remember that a code smell does not mean the code is bad. Only that it 
needs to be looked at a bit more carefully. Perhaps it is bad. Or 
perhaps, like durian fruit, it smells pretty awful but tastes really good.

> There are code smells that are the opposite in fact, methods with long
> parameter lists are generally seen as code smell (?passing a
> paragraph?).

Absolutely! You'll get no disagreement from me there.