Most active commenters
  • hakunin(5)
  • awesome_dude(3)
  • cyberax(3)

←back to thread

1457 points nromiun | 46 comments | | HN request time: 1.499s | source | bottom
Show context
exclipy ◴[] No.45077894[source]
This was my main takeaway from A Philosophy Of Software Design by John Ousterhout. It is the best book on this subject and I recommend it to every software developer.

Basically, you should aim to minimise complexity in software design, but importantly, complexity is defined as "how difficult is it to make changes to it". "How difficult" is largely determined by the amount of cognitive load necessary to understand it.

replies(11): >>45077906 #>>45077954 #>>45078135 #>>45078497 #>>45078728 #>>45078760 #>>45078826 #>>45078970 #>>45079961 #>>45080019 #>>45082718 #
1. YZF ◴[] No.45078760[source]
The problem is no set of rules can replace taste, judgement, experience and intuition. Every rule can be used to argue anything.

You can't win architecture arguments.

I like the article but the people who need it won't understand it and the people who don't need it already know this. As we say, it's not a technical problem, it's always a people and culture problem. Architecture just follows people and culture. If you have Rob Pike and Google you'll get Go. You can't read some book and make Go. (whether you like it or not is a different question).

replies(9): >>45078947 #>>45079055 #>>45079393 #>>45079903 #>>45079931 #>>45079994 #>>45080208 #>>45080993 #>>45083102 #
2. braebo ◴[] No.45078947[source]
I’m accustomed to this principle as a musician, so it’s been interesting to see it withstand my journey into software.
replies(1): >>45079180 #
3. lokar ◴[] No.45079055[source]
I found the book helpful as a way to organize and express what I already knew
4. dlivingston ◴[] No.45079180[source]
Can you expand on this?
replies(1): >>45079886 #
5. zakirullin ◴[] No.45079393[source]
> I like the article but the people who need it won't understand it

That's true. One doesn't change his mindset just after reading. Even after some mentorship the results are far from satisfying. Engineers can completely agree with you on the topic, only to go and do just the opposite.

It seems like the hardest thing to do is to build a feedback loop - "what decisions I made in past -> what it led to". Usually that loop takes a few years to complete, and most people forget that their architecture decisions led to a disaster. Or they just disassociate themselves.

replies(2): >>45080131 #>>45082180 #
6. hakunin ◴[] No.45079886{3}[source]
Not the commenter, but also had experience with making music and writing software. I think the same applies to any creative endeavor. It’s super hard to consume what you produce as “someone else” (I.e. read what you write, listen to what you compose with fresh perspective). Usually it takes time to forget and disassociate from your work, because you get too used to it while producing it. Coming back to it another day can work sometimes, but very quickly you’ll get used to it again. I think this is one of the most effective ways to achieve quality tasteful results in anything. If you can train yourself to read your own code with fresh eyes almost as soon as you write it, you’d be unlocking a powerful shortcut, a cheat code to life. It’ll make the biggest impact on your code’s (and any other creative work’s) quality. This is also why sometimes you can spend hours painstakingly trying to design something, and it comes out terrible, nobody likes it. And you can do something in 20 minutes just improvising your way through, and it comes out an elegant masterpiece. That’s because you never gave yourself time to “get used to” your work such that you couldn’t perceive the problems with it anymore. You maintained that fresh impatient perspective the entire time.
replies(1): >>45080797 #
7. bb88 ◴[] No.45079903[source]
> Every rule can be used to argue anything.

Unless it's a rule prohibiting complexity by removing technologies. Here's a set of rules I have in my head.

1. No multithreading. (See Mozilla's "You must be this high" sign)

2. No visitor pattern. (See grug oriented development)

3. No observer pattern. (See django when signals need to run in a particular order)

4. No custom DSL's. (I need to add a new operator, damnit, and I can't parse your badly written LALR(1) schema).

5. No XML. (Fight me, I have battle scars.)

replies(2): >>45079983 #>>45080703 #
8. bogdanoff_2 ◴[] No.45079931[source]
>the people who need it won't understand it

That's not true. There's plenty of beginner programmers who will benefit from this.

9. ferguess_k ◴[] No.45079983[source]
> 2. No visitor pattern. (See grug oriented development)

This one is my particular pet-peeve. But I often think that the reason is because I suck. I'm going to read "grug".

I also hate one-liner functions.

replies(1): >>45080047 #
10. ruraljuror ◴[] No.45079994[source]
Software developers don’t arrive fully formed. Rob Pike benefitted from reading a book or two.
replies(1): >>45080589 #
11. bb88 ◴[] No.45080047{3}[source]
The real geniuses of our times can convert complexity into simplicity. The subgeniuses use complexity to flex over the common developer.

Sometimes things need to be complex -- well that's okay. The real trick is to not put complexity into places it doesn't belong.

replies(1): >>45088932 #
12. lll-o-lll ◴[] No.45080131[source]
One of the big troubles is that if you join a big org you won’t get to do any architecture until you are at least “senior” or “lead”. Maybe that’s not true everywhere, but I have seen a fair bit of it. You need several iterations of “I built a thing” “oh, the thing evolved in horrible ways”, before the instincts for good architecture are developed.

I think Big Orgs need to develop younger promising talent by letting them build small green fields projects. Essentially fostering startups inside the organisation proper. Let them build and learn from mistakes (while providing the necessary knowledge; you can actually learn most of this from books, but experience is the ultimate teacher). Otherwise you end up with 5 year experienced people who cannot design themselves out of a paper bag.

replies(1): >>45082834 #
13. safety1st ◴[] No.45080208[source]
The approach that I am trialing with my team now, so far to good results, is as follows.

* Our coding standards require that functions have a fairly low cyclomatic complexity. The goal is to ensure that we never have a a function which is really hard to understand.

* We also require a properly descriptive header comment for each function and one of the main emphases in our code reviews is to evaluate the legibility and sensibility of each function signature very carefully. My thinking is the comment sort of describes "developer's intent" whereas the naming of everything in the signature should give you a strong indication of what the function really does.

Now is this going to buy you good architecture for free, of course not.

But what it does seem to do is keep the cognitive load manageable, pretty much all of the time these rules are followed. Understanding a particular bit of the codebase means reading one simple function, and perhaps 1-2 that are related to it.

Granted we are building websites and web applications which are at most medium fancy, not solving NASA problems, but I can say from working with certain parts of the codebase before and after these standards, it's like night and day.

One "sin" this set of rules encourages is that when the logic is unavoidably complex, people are forced to write a function which calls several other functions that are not used anywhere else; it's basically do_thing_a(); do_thing_b(); do_thing_c();. I actually find this to be great because it's easy to notice and tells us what parts of the code are sufficiently complex or awkward as to merit more careful review. Plus, I don't really care that people will say "that's not the right purpose for functions," the reality is that with proper signatures it reads like an easy "cliffs notes" in fairly plain English of exactly what's about to happen, making the code even easier to understand.

replies(6): >>45080627 #>>45080677 #>>45080764 #>>45080785 #>>45081796 #>>45088387 #
14. YZF ◴[] No.45080589[source]
Fair enough. But most of the forming is by doing. Someone gave an analogy to music. You can't become a great musician by reading books. Some great musicians have never read a book about music. But yes, reading can be (a great!) part of the learning process. My point was more about rules. The article says things like replacing complex conditionals with intermediate variables. The idea that a certain construct always have higher cognitive load and should be replaced with another is too simplistic IMO.

In order to get a sense of what code is harder to understand you will do better to read code and have others read your code. A good takeaway is to keep this in mind (amongst many other factors) and to understand code needs to be maintained, extended, adapted etc.

The ideas are still useful. The danger is blindly applying rules. As long as the reader knows not to apply any of the suggestions if they don't understand why and have relevant experience ;)

15. awesome_dude ◴[] No.45080627[source]
> Our coding standards require that functions have a fairly low cyclomatic complexity. The goal is to ensure that we never have a a function which is really hard to understand.

https://github.com/fzipp/gocyclo

> * We also require a properly descriptive header comment for each function and one of the main emphases in our code reviews is to evaluate the legibility and sensibility of each function signature very carefully. My thinking is the comment sort of describes "developer's intent" whereas the naming of everything in the signature should give you a strong indication of what the function really does.

https://github.com/mgechev/revive

> Now is this going to buy you good architecture for free, of course not.

It's not architecture to tell people to comment on their functions.

Also FTR, people confuse cyclomatic complexity for automagically making code confusing to the weirdest example I have ever had to deal with - a team had unilaterally decided that the 'else' keyword could never be used in code.

replies(2): >>45080668 #>>45080713 #
16. jonahx ◴[] No.45080668{3}[source]
> he weirdest example I have ever had to deal with - a team had unilaterally decided that the 'else' keyword could never be used in code.

Not weird at all:

https://medium.com/@matryer/line-of-sight-in-code-186dd7cdea...

replies(1): >>45080730 #
17. cncjchsue7 ◴[] No.45080677[source]
This sounds like hell to me.

Not everything is complicated, most functions don't need comments, why require it? Just fix complexity when it arises. Don't mandate that you can't make any complexity.

replies(2): >>45080763 #>>45080788 #
18. cyberax ◴[] No.45080703[source]
Visitor pattern is extremely useful in some areas, such as compiler development.
replies(1): >>45081398 #
19. arbol ◴[] No.45080713{3}[source]
I can understand why else is sometimes not needed. JS linters will remove unnecessary else statements by default.

https://eslint.org/docs/latest/rules/no-else-return#rule-det...

But never using it is crazy.

replies(1): >>45080740 #
20. awesome_dude ◴[] No.45080730{4}[source]
Well, I found it weird - the else keyword has been a stalwart of programming for... several decades now.

Maybe one day we will abstract it away like the goto keyword (goto is a keyword in Go, and other languages still, but I have only seen it used in the wild once or twice in my 7 or 8 years of writing Go)

Goto is still used in almost every language, but it's abstracted away, hidden in loops, and conditionals (which Djikstra said was a perfectly acceptable use of goto), presumably to discourage its direct use to jump to arbitrary points in the code

replies(1): >>45084683 #
21. awesome_dude ◴[] No.45080740{4}[source]
In a similar vein to how I just responded to the other person, maybe eventually we'll abstract `else` away so that it's use is hidden, and the abstraction ensures that it's only being used where we all collectively decide it can/should be used.
22. cco ◴[] No.45080763{3}[source]
What is a function supposed to do and why?
23. hakunin ◴[] No.45080785[source]
I found this type of approach (where you try to meet subjective readability goals with objective/statistical metrics) to not produce clear code in practice. Instead, I suggest this one weird trick: if your colleagues are confused in code review, then rewrite and comment the code until they aren't confused anymore. Don't just explain it to them ad-hoc, make the code+comments become the explanation. There is no better linter than subjective reading by your colleagues. Nothing else works nearly as well. Optimize to your team's understanding, that's it. Somehow, this tends to keep working great even as the team changes.
replies(1): >>45080960 #
24. bornfreddy ◴[] No.45080788{3}[source]
Agreed. If you need a comment to tell you what the function does, you should think deep about naming, and if this fails, consider if this is the correct abstraction. Comments are a way to kick the can down the road - "I was unable to make this code clear enough, so here is the hint to help you".

Edit: sometimes the comments are the best of all evils, and you should use them to explain the constraints that led to this code - they just shouldn't be mandatory.

25. teiferer ◴[] No.45080797{4}[source]
> If you can train yourself to read your own code with fresh eyes almost as soon as you write it, you’d be unlocking a powerful shortcut, a cheat code to life.

This is really a key takeaway here: Always keep your audience in mind. When programming, you have two audiences: the machine executing the code, and fellow programmers maintaining the code. Both are important, but the latter is often neglected and is what the article is about. Optimize for your human audience. What will make it easier for the next person to understand this? Do that.

Like public speaking or writing an article. A great talk or a article happen when the speaker/author knew exactly how the audience would perceive them.

replies(1): >>45080840 #
26. hakunin ◴[] No.45080840{5}[source]
Agreed, I wrote more in depth about it a few years ago: https://max.engineer/maintainable-code
27. necovek ◴[] No.45080960{3}[source]
It's one message I struggle to convey to people I do code reviews for: don't make me understand it, make it more self explanatory so every reader does. (And, yes, I ask for it explicitly too)

(I sometimes "ask" questions for something it took me a few back and forths through code to get so they'd think about how it could be made clearer)

Unfortunately, most people focus on explaining their frame of mind (insecurity?) instead of thinking how can they be the best "teacher".

replies(1): >>45081016 #
28. mnsc ◴[] No.45080993[source]
> You can't win architecture arguments.

I feel this in my soul. But I'm starting to understand this and accept it. Acceptance seem to lessen my frustration on discussing with architects that seemingly always take the opposite stance to me. There is no right or wrong, just always different trade offs depending on what rule or constraint you are prioritizing in your mind.

replies(3): >>45081113 #>>45081624 #>>45083244 #
29. hakunin ◴[] No.45081016{4}[source]
Yeah, not easy, but it helps to build some rapport first, so people learn what you’re after. The way I tend to do that is by leaving a review comment with an example code snippet that makes me understand it better, and a question “what do you think about this version? I tried to clarify a few things here.”. + Explain what was clarified. I find the effort usually pays off.
replies(2): >>45081330 #>>45081735 #
30. berkes ◴[] No.45081113[source]
I've found that listening and asking questions is the key to accepting other people's architectural choices.

Why do they insist on A over B? What trade offs were considered? Why are these trade offs less threatening than other trade offs? What previous failures or difficulties led them to put such weight on this problem over others?

Sometimes it's just ego or stubbornness or routine¹. That can and should be dismissed IMO. Even if through these misguided reasons they choose the "right" architecture, even if the outcome turns out good, that way of working is toxic and bad for any long term project.

More often, there are good, solid reasons behind choices, though. Backed with data or science even. Things I didn't know, or see different, or have data and scientific papers for that "prove" the exact opposite. But it doesn't matter that much, as long as we all understand what we are prioritizing, what the trade offs are and how we mitigate the risks of those trade offs, it's fine.

¹ The worst, IMO, is the "we've always done it like this" trench. An ego can be softened or taken off the team. But unwillingness to learn and change, instilled in team culture is an almost guaranteed recipe for disaster

31. radiator ◴[] No.45081330{5}[source]
But this might require too much effort from the reviewer
32. brabel ◴[] No.45081398{3}[source]
That’s only true in languages that do not have Algebraic Data Types and pattern matching, which nowadays is a minority of languages (even Java has it).
replies(1): >>45081544 #
33. cyberax ◴[] No.45081544{4}[source]
Visitors additionally allow you to decouple graph traversal from the processing. It is still needed even in the languages with pattern matching.

There's also the question of exhaustiveness checking. With visitors, you can typically opt-in to either checking that you handle everything. Or use the default no-ops for anything that you're not interested in.

So if you look at compilers for languages with pattern matching (e.g. Rust), you still see... visitors! E.g.: https://github.com/rust-lang/rust/blob/64a99db105f45ea330473...

replies(1): >>45082244 #
34. KronisLV ◴[] No.45081624[source]
> Acceptance seem to lessen my frustration on discussing with architects that seemingly always take the opposite stance to me. There is no right or wrong, just always different trade offs depending on what rule or constraint you are prioritizing in your mind.

That’s a stance of acceptance, however I’d say that there are people who are absolutely wrong by most metrics sometimes and also stubborn to the point that you’ll never convince them. Ergo, the frustration is inevitable when faced with them.

35. necovek ◴[] No.45081735{5}[source]
I found that to be a double edged sword: some copy and paste it verbatim without thinking it through and adjusting at all.

It's a delicate balance we need to keep in mind between many of:

- maintainable code

- getting things done

- feeling of accomplishment

- feedback loop speed

- coaching

- motivation and emotional state ("why are they pestering me, the code works, I just want to feel productive and useful: this was hard enough to get right as it is")

...and more!

At the same time, some do get the point, but getting readable code is really an art/craft in itself, and nothing but experience and learning to look at it from outside is the main driver to learning.

replies(1): >>45085144 #
36. serpix ◴[] No.45081796[source]
These points are about organising code and workflow. Even if you have organised your functions to the lowest possible unit of work you can still have a mess of async queue microservice hell which is the actual architecture.

Architecture is another topic entirely and the scope is higher abstractions across multiple systems.

37. wreath ◴[] No.45082180[source]
In an industry where most people stay for around 2 years (at least pre 2022), people arent even there to see the results of their decisions.
38. brabel ◴[] No.45082244{5}[source]
The example you posted is very interesting as it used both a visitor and ADTs. It seems the need for the Visitor comes from the generics in this case? Probably a Rust specific limitation. I don’t understand why you mention exhaustiveness though, it’s obviously easy have comprehensive or partial matching with ADT.
replies(1): >>45084559 #
39. fireflash38 ◴[] No.45082834{3}[source]
It's not helped by the "jump every 2 years for 20-50% pay bump". They don't have to deal with their own architectural decisions.
40. heresie-dabord ◴[] No.45083102[source]
> Every rule can be used to argue anything.

This is true. However, very few people can clearly explain all the rules.

If they can, they have understood the system and are qualified.

41. vitaflo ◴[] No.45083244[source]
The problem with a lot of devs is trying to win arguments instead of coming to a consensus. What’s best for the team matters more than what’s best for the individual and every team is different.
42. cyberax ◴[] No.45084559{6}[source]
No. The code can be rewritten without visitors using iterators for traversal, for example). But it'll look badly.

Visitors in the linked example are real classic visitors. The code _within_ the visitor methods, of course, uses pattern matching, but the pattern itself is not materially different from C++.

Exhaustiveness checking for pattern matching is also "best effort" for complex matching.

43. jonahx ◴[] No.45084683{5}[source]
In a sense, all of these coding practices -- whether restricting goto to loops and conditionals, which has broad acceptance these days, or avoiding else to "keep the happy left", or anything else in a coding style guide -- are just doing one thing: restricting the language to a smaller subset of itself.

And in general the primary benefit of such restriction is to reduce cognitive load. Scheme is easier than C++. The downside of such restriction is loss of expressiveness. Whether the net benefit is good depends on how these two things trade off. Experience and developer preference are inputs to that equation, which is why devs fight over coding guidelines. But I think it's helpful to boil it down in this way at a high level.

The ideal is smaller language where the expressiveness you've cut away is only rarely useful, and often error-prone.

44. hakunin ◴[] No.45085144{6}[source]
Yeah, this does require a certain team culture building effort. Just starting cold without any expectation-setting might not be received well.

One "rule" I try to meta-promote is — working code is the first step, and a great foundation to then proceed to clear and maintainable code.

Another, is that code reviews are first-class citizens deserving mindfulness.

45. exclipy ◴[] No.45088387[source]
I actually think this is antithetical to the philosophy. Cyclometic complexity is very much not the same as "is this code difficult to understand".

Arbitrary structure rules like "do_thing_a(); do_thing_b(); do_thing_c();" also is not unless you can explain how this helps make it easier to understand compared to say, one big function with "// DO THING A" comments.

46. chanux ◴[] No.45088932{4}[source]
Complexity has to live somewhere. The genius is in putting in places that make things manageable, I guess.

https://ferd.ca/complexity-has-to-live-somewhere.html