Live data from Hacker News

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

seeinglogic.com

171–180 of 383 posts

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

#171
post #170

Earlier quoted context omitted.

That is exactly what is discussed in: https://www.goodreads.com/book/show/39996759-a-philosophy-of...

Man, this is the third reference to this book I am seeing this week, I need to order this book.

Gave away a copy to a developer in Brazil, and ordered the Kindle version, and need to order another print copy.

Can't recommend it highly enough --- I found it transformative --- read through it one chapter at a time, then re-worked the code of my current project:

https://github.com/WillAdams/gcodepreview

then went on to the next chapter --- about the halfway point I had the code cleaned up so well that the changes became quite minor.

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

#172

Earlier quoted context omitted.

Then what does the function that calls the split functions get called? foo_and_bar_and_qoo? And if they’re called only under some conditions?

It likely has some higher-level meaning other than just do foo, bar, qoo. For example if you are calling functions "openDishwasher", "loadDishwasher", "closeDishwasher", "startDishwasher", your function should be called "washDishes". Not always that straightforward, but I believe in 95% it's not difficult to put a name on that. For the rest 5% you need to get creative, or maybe you realize that you didn't group the f…

Yeah, I agree in spirit but I think the answer is more ”it depends” than something where you should feel bad or something if you deviate from it. If washDishes also sends a bunch of metrics/diagnostics or updates a database of your favorite dish washing programs somewhere inside it, that’s probably fine. Otherwise you push the path of least resistance to just be vague instead, then you get a codebase full of functions with names like handle or process.

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

#173

Earlier quoted context omitted.

True, but now you're relying on a specific implementation and optimization of the compiler, unless the language semantics explicitly say that lambdas will be inlined.

This is true of literally anything and everything your compiler emits. In practice the functional style is much easier to optimize to a far greater degree than the imperative style.

This is why you shouldn't get into arguments about performance on the internet without highly specified execution environments.

I'm going to take my own advice and go back to work :)

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

#174
post #115

Earlier quoted context omitted.

authors_of_long_books = set() for book in books: if len(book.pages) > 1000: authors_of_long_books.add(book.author) return authors_of_long_books You are told explicitly at the beginning what the type of the result will be, you see that it's a single pass over books and that we're matching based on page count. There are no intermediate results to think about and no function call overhead. When you read it out loud it's…

fwiw, once Python's introduced there's the third option on the table, comprehensions, which will also be suggested by linters to avoid lambdas: authors_of_long_books: set[Author] = {book.author for book in books if book.page_count > 1000} These are somewhat contentious as they can get overly complex, but for this case it should be small & clear enough for any Python programmer.

I tried scaling up the original into an intentionally convoluted nonsensical problem to see how a more complicated solution would look like for each approach. Do these look right? And which seems the most readable?

  # Functional approach
  
  var favoriteFoodsOfFurryPetsOfFamousAuthorsOfLongChineseBooksAboutHistory = books
    .filter(book => 
       book.pageCount > 100 and 
       book.language == "Chinese" and 
       book.subject == "History" and
       book.author.mentions > 10_000
    )
    .flatMap(book => book.author.pets)
    .filter(pet => pet.is_furry)
    .map(pet => pet.favoriteFood)
    .distinct()

  # Procedural approach
  
  var favoriteFoodsOfFurryPetsOfFamousAuthorsOfLongChineseBooksAboutHistory = set()
  for book in books:
    if len(book.pageCount > 100) and
       book.language == "Chinese" and
       book.subject == "History" and
       book.author.mentions > 10_000:
      for pet in book.author.pets:
        if pet.is_furry:
          favoriteFoodsOfFurryPetsOfFamousAuthorsOfLongChineseBooksAboutHistory.add(pet.favoriteFood)

  # Comprehension approach
  
  var favoriteFoodsOfFurryPetsOfFamousAuthorsOfLongChineseBooksAboutHistory = {
    pet.favoriteFood for pet in
      pets for pets in 
        [book.author.pets for book in 
          books if len(book.pageCount > 100) and
                   book.language == "Chinese" and
                   book.subject == "History" and
                   book.author.mentions > 10_000]
    if pet.is_furry
  }
FWIW, for more complex problems, I think the second one is the most readable.

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

#175
post #129

Earlier quoted context omitted.

fwiw, once Python's introduced there's the third option on the table, comprehensions, which will also be suggested by linters to avoid lambdas: authors_of_long_books: set[Author] = {book.author for book in books if book.page_count > 1000} These are somewhat contentious as they can get overly complex, but for this case it should be small & clear enough for any Python programmer.

Without syntax highlighting, "book.author for book in books if book.page_count > 1000" requires a lot more effort to parse because white space like newlines is not being used to separate things out.

    authors_of_long_books: set[Author] = {
        book.author 
        for book in books 
        if book.page_count > 1000
    }

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

#176
post #109

> Chaining together map/reduce/filter and other functional programming constructs (lambdas, iterators, comprehensions) may be concise, but long/multiple chains hurt readability This is not at all implied by anything else in the article. This feels like a common "I'm unfamiliar with it so it's bad" gripe that the author just sneaked in. Once you become a little familiar with it, it's usually far easier to both read an…

Your example is a conceptually simple filter on a single list of items. But once the chain grows too long, the conditions become too complex, and there are too many lists/variables involved, it becomes impossible understand everything at once. In a procedural loop, you can assign an intermediate result to a variable. By giving it a name, you can forget the processing you have done so far and focus on the next steps.

In a practical example you'd create a named intermediate type which becomes a new base for reasoning. Once you convinced yourself that the first part of the chain responsible for creating that type (or a collection of it) is correct, you can forget it and free up working memory to move on to the next part. The pure nature of the steps also makes them trivially testable as you can just call them individually with easy to construct values.

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

#177
post #10

Earlier quoted context omitted.

> the answer isn't always splitting it into 5 smaller ones To someone who just read a book about it, it is. I've heard this called "rabbit hole" programming; it's function after function after function, with no apparent reason for them other than the line count. It's maddening.

I think the whole Uncle Bob clean code movement has a lot to answer for.

https://github.com/johnousterhout/aposd-vs-clean-code

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

#178

Earlier quoted context omitted.

That isn't natural to anyone who is not intimately familiar with procedural programming. This is not about "procedural programming" - this is exactly how this works mentally. For kicks I just asked me 11-year old kid to write down names of all the books behind her desk (20-ish) of them and give me names of authors of books that are 200 pages or more. She "procedurally" 1. took a book 2. flipped to last page to see pa…

That's when you're doing the job, not what the mental representation of the solution. I strongly believe if you ask her to describe the task, she would go: 1. (Take the books)->(that have 200 pages or more)->(and mark down the name of the authors)->(only once)

I respectfully disagree. And I think one of the core reason SWEs struggle with functional-style of programming is that it is neither intuitive nor how general-joe-doe’s brain works.

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

#179

Author here. Thank you for all the thoughtful comments and great stuff I didn't think of (also has been a hot minute since I wrote the article). I appreciate the discussion!

This is, by a large margin, the best article about code complexity I have read in a looong time. Thanks.

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

#180

Earlier quoted context omitted.

You don't ever need to "understand everything at once". You can read each stanza linearly. The for loop style is the approach where everything often needs to be understood all at once since the logic is interspersed throughout the entire body.

In the example above, you first have a list of books. Then you filter it down to books with >1000 pages. Then you map it to authors of books with >1000 pages. Then you collapse it to distinct authors of books with >1000 pages. Every step in the chain adds further complexity to the description of the things you have, until it exceeds the capacity of your working memory. Then you can no longer reason about it. The stan…

Folks who are familiar with chaining don't think about it in the way that you've presented. If you're familiar, it's more like:

Filter to the books with >1000 pages

Then their authors.

Finally, distinguish those authors.

If you're familiar, you don't mentally represent each link in the chain as the totality of everything that came before it _plus_ whatever operation you're doing now. You consider each link in the chain in isolation, as its inputs are the prior link and its outputs will be used in the next link. Giving a name to each one of those links in the chain isn't always necessary, and depending on how trivial the operations are, can really hurt readability.

I think its very much a personal preference.

Post reply on HN