Hacker News new | ask | show | jobs
by fr4nkr 751 days ago
I would argue that what constitutes clever code varies a lot by language. There's always a "cleverness" threshold where being able to read or refactor the code becomes harder, but this threshold isn't universal.

Python in particular makes it very easy to be too clever, since its extremely rigid syntax was designed specifically to discourage it, but it ended up giving the user the necessary tools to be clever anyway, and the end result is usually... not pretty.

2 comments

What would qualify as clever Python? These kind of broad and vague statements make me wonder if I am guilty..
For a simple example, I think the walrus operator (:=) could be considered clever. I like it, and use it, but the fact that you can declare a variable, store a value in it, and then perform actions depending on its value, all in one line, gives me pause.

    if (foo := bar()) is not None:
        baz(foo)
Whereas the traditionally accepted Python method of dealing with this would be EAFP:

    try:
        foo = bar()
        baz(foo)
    except AttributeError:
        # handle exception
What? Those aren't equivalent at all.

Where are you expecting an AttributeError to come from? Why are you comfortable catching them from anything inside of the baz(...) invocation?

The traditional method would be to just bind it and check for a null outside the expression.

    foo = bar()
    if foo is not None:
        baz(foo)
I don't know why people insisted on pretending binding variables before using them was such a difficulty that it was worth altering the language. Lazy and bad programmers aren't going to stop being lazy and bad when you hand them the ability to name things willy nilly. They'll just use that badly as well.
The assumption in the example is that baz() required foo to not be None; attempting to use None where you expect a string will probably in an AttributeError.

As to your example, both LBYL and EAFP are accepted Python standards.

In your first example I think most people would skip the 'is not None' as None is already falsy and drop the extra braces.

I find it visually clearer than the second example where the cause and effect are slightly separated, but I do agree there's an element of cleverness to it.

But that would be a mistake if you need the branch to run for other falsely values like 0 or the empty string.
In this imaginary example, yes, a False is likely to cause the same issues that None would. But as a sibling comment mentions, it’s not always desirable to drop the explicitness for syntactic sugar.
I currently have a dilemma regarding a XML generation code. Should I write it in pure Python with endless nested loops and DOM manipulations. Or should I use a templating engine that is exactly the DSL [:domain specific language] to express declaratively how to generate the output.

Solution 2 is superbly consise and straight to the point, but I am pretty sure noone will ever climb the learning curve that this additional DSL imposes to any newcomer.

I really wonder which final choice I will make for my production code.

[truth is that refactoring solution 2 is painless, but at the same time debugging solution 2 is tricky]

Can you write functions and classes in Python that roughly mimic the DSL you're aiming for? This is often called an embedded DSL, and it's a great technique for allowing developers to work with domain objects inside a language that they're still familiar with.

There's a handful of templating languages that do this sort of thing using functions, so in Python you might write

    form(
        {"method": "POST"},
        label(
            "Name",
            input({"type": "text"}),
        ),
    )
The learning curve becomes significantly lighter because you're just using Python constructs in the Python language, which means your IDE can suggest functions to you like it would with any other library.
TL;DR

- OOP obsession is a common python developer phase. it's a dangerous phase.

- maintainable code is not minified code. minified code is minified code.

- stoopid code is often stoopid enough when it satisfies real world / human concerns, not technical concerns.

----

I've done all of these, and I see other people repeating them.

1. hyper optimised and utterly fragile class based inheritance / abstractions. Not optimised in performance. Optimised in terms of minimisation of code. avoid ABCs like the plague. YAGNI so save yourself some heartache and keep it stoopid.

2. especially when ^ includes many static methods that could be standalone functions. a good sign the code can probably refactor to functional + objects/dataclasses (and probably be easier to test as a result). You didn't need it, go back to keeping it stoopid.

3. methods that call another method, that calls another method, that eventually calls one of the static methods. often when I see this, none of these child methods are called by anything else. someone wrote multiple separate methods because apparently we shouldn't write methods with more than 10 LoC. because that's how to write clean code apparently. just put it all in a single method so I don't have open multiple different browser tabs while sitting in the doctor's office responding to an incident on my phone. stop optimising for LoC and make it obviously stoopid.

4. hiding how the code will run away from main entrypoint by adding a `className.run()` method. yeah, cool, your main function has been minified. kudos. But now I have no idea what steps your script will run. I have to go and read something else. make it obviously stoopid to someone reading this for the first time.

5. using names of concepts from other languages. don't call classes an "Interface" or a "Controller" because it sounds better. This isn't Java nor is it Kubernetes. It's python. keep names so stoopid that I can understand when my phone has woken me up at 3 am.

6. functional is usually simpler, until you start turning in a mathematician. you are not a mathematician. and neither is the junior sitting next to you. don't overuse recursion or currying etc. keep it stoopid enough that the junior sitting next to you has a chance of taking over responsibility for it one day without going through a PhD in mathematics.

7. avoid using functionality from the last 3x minor versions of python [0]. slow down and let others catch up first so we can all be stoopid together.

----

caveat: experience will vary wildly between different hoomans regarding what is considered stoopid enough.

[0]: a good exception here is something like case matching. this was pretty big so I would have allowed that, so long as everyone was aware it was a new thing now (I'd have done a post on slack saying -- Oi, go look at this, it's big)

I find functional programming is where things get bad. It does give some pretty elegant ways to do things, but it can become a nightmare when debugging. Sometimes I need to dig into Django code to work out what's going on. It's all fine while the code is imperative, but as soon as you hit a functional part, then following what's going on gets difficult.
> It's all fine while the code is imperative

ah sorry, i may have had a brain fart here. I use functions imperatively like you say and that's the concept i wanted to get across. failed to do so because I just mentioned don't over-do it on the math.

my bad.

should probably have been an extra one between 6 + 7 for do things imperatively

Coming from Perl, Python made me write more verbose simple code. But I noticed that in Perl I would do something clever, then a few days later it would come back and bit me in the arse, when I was trying to debug it. Sure Perl saved a few lines of code over Python, but my Python code ended up more reliable. I do miss Perl though.