Live data from Hacker News

What makes code hard to read: Visual patterns of complexity (2023)

seeinglogic.com

241–250 of 383 posts

Re: What makes code hard to read: Visual patterns of complexity (2023)

#241

You really should try to pack an unbroken thought as a single line of code as much as possible. That’s the idea behind chaining multiple functions together on one line instead of spreading it out over several lines. Eyes go horizontally more naturally than up and down, it fits our vision’s natural aspect ratio. And stop making deep nestings. Making a single function call per line assigning output to a variable each t…

I agree with you on piping but writing each method on its own line makes the code very approachable (also easier to work with). Consider this code (from a course I'm teaching this week): (df .pipe(lambda df_: print(df_.columns) or df_) .groupby('activity_id', observed=True) [non_agg_cols] .apply(lambda g: g.assign(distance=calculate_distance_np(g)), include_groups=True) .pipe(fix_index) .pipe(lambda df_: print('DONE!…

I don’t really think the first example is easier to read, it’s more of an illusion. A skilled reader should be able to carry the context as they read along. It is only because we occasionally work with inexperienced coders that the list style is necessary. Consider:

“The quick brown fox jumped over the lazy ass dog”

Vs

The quick brown fox

jumped over

the lazy ass dog

The second example helps a reader understand the subjects and action but it is wholly unnecessary for people who know how to read.

Re: What makes code hard to read: Visual patterns of complexity (2023)

#242

Earlier quoted context omitted.

> The problem is that it's easy to overdo it. Welcome to all features of every programming language? Sacrificing readability, optimization, and simplicity for the 95% case because some un-principled developers might overdo it in the 5% case (when the cost of fixing it is trivially just inserting variable assignments) is… not a good trade-off.

5% is common enough that you'll encounter it almost every time you read code. And fixing it is not easy, because you first need to understand the code before you can add useful variable names. Besides, programming language evolution is mostly driven by the fact that everyone is lazy and unprincipled at least occasionally. If you need to be disciplined to avoid footguns, you'll trigger them sooner or later.

5% is also low enough that you can just use another technique for the exceptions.

Re: What makes code hard to read: Visual patterns of complexity (2023)

#243

Earlier quoted context omitted.

You have literally just described the set of objects asked for: the unique authors of the books with more than 1,000 pages. I don't understand how you expect to get any simpler than that. The functional style isn't even requiring you to describe how to accomplish it, it almost verbatim simply describes the answer you're trying to get. If your entire objection is that you might want intermediate-named variables… you c…

The problem is that it's easy to overdo it. When you are writing the code, you already know what it's supposed to do, and adding a few more things to the chain is convenient and attractive. But when you are reading unfamiliar code, you often wish that the author was more explicit with their code. Not just with what the code is actually doing, but what it's trying to do and what are the key waypoints to get there. Wit…

> But in functional code, the entire chain is a single statement

Not necessarily. You can use intermediate variables when necessary.

Re: What makes code hard to read: Visual patterns of complexity (2023)

#244
post #236

Earlier quoted context omitted.

It's also harder to write and debug with the intermediate steps.

How so? The states of the intermediate steps are logically and easily exposed in a debugger. You can also easily set conditional breakpoints relative to the intermediate states. I know that intermediate states are generally easier to comprehend, because I never have to explain them in code reviews. To avoid having to explain chains to others, I end up having to add descriptive comments to the intermediate steps, far…

Build up and debug the chain as you work in an environment like Jupyter. No need to create variables. Just run the code and verify that the current step works. Then, proceed to the next. Then, put the chain in a function. If you want to be nice, put a .loc as the first step to explicitly list all of the input columns. Drop another .loc as the last step to validate the output columns. (This also serves as a test and documentation to future you about what needs to come in and out.) Create a simple unit test with a sample of the data if you desire.

I've found that the constraint of thinking in chains forces me to think of the recipe that I need for my data. Of course, not everything can be done in a stepwise manner (.pipe helps with that), but often, this constraint forces you to think about what you are doing.

Every good Pandas user I know uses it this way. I've taught hundreds more. Generally, it feels weird at first (kind of like whitespace in Python), but after a day, you get used to it.

Do you store intermediate results of SQL?

Re: What makes code hard to read: Visual patterns of complexity (2023)

#245

Earlier quoted context omitted.

I agree with you on piping but writing each method on its own line makes the code very approachable (also easier to work with). Consider this code (from a course I'm teaching this week): (df .pipe(lambda df_: print(df_.columns) or df_) .groupby('activity_id', observed=True) [non_agg_cols] .apply(lambda g: g.assign(distance=calculate_distance_np(g)), include_groups=True) .pipe(fix_index) .pipe(lambda df_: print('DONE!…

I don’t really think the first example is easier to read, it’s more of an illusion. A skilled reader should be able to carry the context as they read along. It is only because we occasionally work with inexperienced coders that the list style is necessary. Consider: “The quick brown fox jumped over the lazy ass dog” Vs The quick brown fox jumped over the lazy ass dog The second example helps a reader understand the s…

It is certainly easier to work with the former. If you need to comment out a line, it is painless.

Re: What makes code hard to read: Visual patterns of complexity (2023)

#246
post #55

Earlier quoted context omitted.

> I meant the goal of your function needs to be grasped within a reasonable amount of time. This works for every codebase. It really doesn't though. Here's a function of mine. It's maybe 40 lines of logic, so medium-scale. It's part of an intrusive red/black tree implementation for Zephyr. I'm fairly proud of how it turned out, and think that this code is awfully readable given its constraints. No human being is goin…

This is exactly the kind of example I have in my head for code that constitutes a high level of information density. Adding abstraction and 'literate' constructs to try and make things readable is ultimately deferring the fact that understanding the code here is fundamentally about understanding a specific implementation of an algorithm, and to understand _that_ ultimately needs the reader to build their own clear me…

> Maybe it's a defeatist attitude, but I feel like sometimes the problem is the problem, and pushing abstractions only works to defer the requirements to understand it

That's also my impression and experience.

And sometimes there is no problem at all, but abstractions are still pushed too far and then a problem arises in the form of non-essential complexity.

Re: What makes code hard to read: Visual patterns of complexity (2023)

#247
As someone with relatively bad memory, in general less variables = easier to understand. The more I need to remember, the more time I'll spent. IDEs help by providing inline hints and documentation, but the main issue is that a variable _can_ be used later, so you need to remember it. No variable (like on a chained/piped construct) means that the value is generated and immediately used.

Re: What makes code hard to read: Visual patterns of complexity (2023)

#248
post #61

My pet peeve: function getOddness4(n: number): if (n % 2 === 0): return "Even"; return "Odd"; While it is shorter, I prefer vastly prefer this one: function getOddness2(n: number): if (n % 2 === 0): return "Even"; else: return "Odd"; Reason: getOddness4 gives some sense of asymmetry, whereas "Even" and "Odd" are symmetric choices. getOddness2 is in that respect straightforward.

If it's this short, the ternary operator would be the absolute best option IMHO.

If any of the clauses are much longer, the first option reads a lot better if it can be a guard cause that returns very quick.

If neither options are short I'd argue they should be pushed away into scoped and named blocks (e.g. a function) and we're back to either a ternary operation or a guard like clause.

Re: What makes code hard to read: Visual patterns of complexity (2023)

#249
post #219

Earlier quoted context omitted.

The dig on chains of map/reduce/filter was listed as a "Halstead Complexity Takeaway", and seemed to come out of the blue, unjustified by any of the points made about Halstead complexity. In fact in your later funcA vs. funcB example, funcB would seem to have higher Halstead complexity due to its additional variables (depending on whether they count as additional "operands" or not). In general, long chains of functio…

o_node := graph.GetNodeByName(name) var ret []string for _, node := range o_node.connectedNodes() { if !node.isHidden { ret = append(ret, node.name) } } return ret

There is just no way that reasonable people consider this to be clearer. One certainly might be more familiar with this approach, but it is less clear by a long shot.

You've added a temp variable for the result, manual appending to that temp variable (which introduces a performance regression from having to periodically grow the array), loop variables, unused variables, multiple layers of nesting, and conditional logic. And the logic itself is no longer conceptually linear.

Re: What makes code hard to read: Visual patterns of complexity (2023)

#250

Earlier quoted context omitted.

You have literally just described the set of objects asked for: the unique authors of the books with more than 1,000 pages. I don't understand how you expect to get any simpler than that. The functional style isn't even requiring you to describe how to accomplish it, it almost verbatim simply describes the answer you're trying to get. If your entire objection is that you might want intermediate-named variables… you c…

The problem is that it's easy to overdo it. When you are writing the code, you already know what it's supposed to do, and adding a few more things to the chain is convenient and attractive. But when you are reading unfamiliar code, you often wish that the author was more explicit with their code. Not just with what the code is actually doing, but what it's trying to do and what are the key waypoints to get there. Wit…

> But in functional code, the entire chain is a single statement. There are no natural breakpoints where the reader could expect to find justifications for the code.

How are we deciding what's "functional code", here? Because functional languages also provide means like `let` and `where` bindings to break up statements. The example might in pseudo-Haskell be broken up like

    distinctAuthors = distinct authors
      where
        authors = map (\book -> book.author) longBooks
        longBooks = filter (\book -> book.pageCount > 1000) books

IMO the code here is also simple enough that I don't see it needing much in the way of comments, but it is also possible and common to intersperse comments in the dot style, e.g.

    distinctAuthors = books // TODO: Where does this collection come from anyway?
        // books are officially considered long if they're over 1000 pages, c.f. the Council of Chalcedon (451)
        .filter(book => book.pageCount > 1000)
        // All books have exactly one author for some reason. Why? Shouldn't this be a flatmap or something?
        .map(book => book.author)
        // We obviously actually want a set[author] here, rather than a pruned list[author],
        // but in this imaginary DinkyLang we'd have to implement that as map[author, null]
        // and that's just too annoying to deal with
        .distinct()
Post reply on HN