top | item 45077894

(no title)

exclipy | 6 months ago

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.

discuss

order

YZF|6 months ago

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).

safety1st|6 months ago

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.

bogdanoff_2|6 months ago

>the people who need it won't understand it

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

braebo|6 months ago

I’m accustomed to this principle as a musician, so it’s been interesting to see it withstand my journey into software.

ruraljuror|6 months ago

Software developers don’t arrive fully formed. Rob Pike benefitted from reading a book or two.

zakirullin|6 months ago

> 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.

bb88|6 months ago

> 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.)

mnsc|6 months ago

> 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.

lokar|6 months ago

I found the book helpful as a way to organize and express what I already knew

heresie-dabord|6 months ago

> 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.

bsenftner|6 months ago

Which is why I consider DRY (Don't Repeat Yourself) to be an anti-rule until an application is fairly well understood and multiple versions exist. DO repeat yourself, and do not create some smart version of what you think the problem is before you're attempting the 3rd version. Version 1 is how you figure out the problem space, version 2 is how you figure out your solution as a maintainable dynamic thing within a changing tech landscape, and version 3 is when DRY is look at for the first time for that application.

zahlman|6 months ago

DRY isn't about not reimplementing things; it's about not literally copying and pasting code. Which I have seen all the time, and which some might find easier now but will definitely make the system harder to change (correctly) at some point later on.

martinpw|6 months ago

Closely related to the Rule of Three - ok to duplicate once, but if it is needed a third time, consider refactoring: https://en.wikipedia.org/wiki/Rule_of_three_(computer_progra...

I think it's a pretty good compromise. I have tried in the past not to duplicate code at all, and it often ends up more pain than gain. Allow copy/paste if code is needed in two different places, but refactor if needed in three or more, is a pretty good rule of thumb.

cyberax|6 months ago

DRY means something completely different. It means that there should be just one source of truth.

Example: you have a config defined as Java/Go classes/structures. You want to check that the config file has the correct syntax. Non-DRY strategy is to describe its structure in an XSD schema (ok, ok JSON schema) and then validate the config. So you end up with two sources of truth: the schema and Java/Go classes, they can drift apart and cause problems.

The DRY way is to generate the classes/structures that define the config from that schema.

hinkley|6 months ago

Some people use a gardening metaphor for code, and I think that since code is from and for humans, that’s not a terrible analogy. It’s organic by origin if not by nature.

When you’re dealing with perennial plants, there’s only so much control you actually have, and there’s a list of things you know you have to do with them but you cannot do them all at once. There is what you need to do now, what you need to do next year, and a theory of what you’ll do over the next five years. And two years into any five year plan, the five year plan has completely changed. You’re hedging your bets.

Traditional Formal English and French gardens try to “master” the plants. Force them to behave to an exacting standard. It’s only possible with both a high degree of skill and a vast pool of labor. They aren’t really about nature, or food. They’re displays of opulence. They are conspicuous consumption. They are keeping up with the Joneses. Some people love that about them. More practical people see it as pretentious bullshit.

I think we all know a few companies that make a bad idea work by sheer force of will and overwhelming resources.

tialaramex|6 months ago

I think more than a few people have recommended waiting until the 3rd or 4th X before you say OK, Don't Repeat Yourself we need to factor this out. That's where my rule of thumb is too.

Deliberately going earlier makes sense if experience teaches you there will be 3+ of this eventually, but the point where I'm going to pick "Decline" and write that you need to fix this first is when I see you've repeated something 4-5 times, that's too many, we have machines to do repetition for us, have the machine do it.

An EnableEditor function? OK, meaningful name. EnablePublisher? Hmm, yes I understand the name scheme but I get a bad feeling. EnableCoAuthor? Approved with a stern note to reconsider, are we really never adding more of these, is there really some reason you can't factor this out? EnableAuditor. No. Stop, this function is named Enable and it takes a Role, do not copy-paste and change the names.

hinkley|6 months ago

It’s a pain in the ass to source a copy of this book without giving Jeff Bezos all the money. If anyone reading this thread knows John, could you bring this to his attention?

I even tried calling the bookstore on his campus and they said try back at the beginning of a semester, they didn’t have any copies.

My local book store could not source me a copy, and neither IIRC could Powell’s.

tialaramex|6 months ago

That sucks. Ordinarily although a weird volume there's no demand for won't be fast a bookshop should be able to get anything in print. Is there some reason it's specific to this book do you think?

zakirullin|6 months ago

That's best book on the topic! The article was inspired by this exact book. And John is a very good person, we discussed a thing or two about the article.

exclipy|6 months ago

Oh! I was surprised you didn't link or mention the book

swat535|6 months ago

I've long given up on trying to find the perfect solution for Software. I don't think anyone has really "cracked the code" per se. The best we have is people's wisdom and experiences.

Ultimately, context, industries and teams vary so greatly that it doesn't make sense to quantify it.

What I've settled on instead is aiming for a balance between "mess" and "beauty" in my design. The hardest thing for me personally to grasp was that businesses are indeterministic whereas software is not, thus requirements always shifts and fitting this into the rigidity of computer systems is _difficult_.

These days, I only attempt to refactor when I start to feel the pain when I'm about to change the code.. and even then, I perform the bare minimum to clean up the code. Eventually multiple refactoring shapes a new pattern which can be pulled into an abstraction.

epolanski|6 months ago

There is no objective "perfect", because the "perfect" is in the eyes of the reader.

Also, people confuse familiar with simple, they tend to find things simple if they are familiar, even if they are complex (interwine a lot of different things).

ferguess_k|6 months ago

I'm struggling with the amount of complexity. As an inexperienced SWE, I found it difficult to put everything into my head when the # of function calls (A) + # of source code files (B) to navigate reach N. In particular, if B >= 3 or A >= 3 -- because, B equals the number of screens I need to view all source code files without Command+Tab/Alt+Tab, and cognitive load increases when A increases, especially when some "patterns" are involved.

But I'm not experienced enough to tell, whether it is my inexperience that causes the difficulty, or it is indeed that the unnecessary complexity tha causes it.

lll-o-lll|6 months ago

Humans have a very limited amount of working memory. 3-5 items on average. A savant might be at something like 12. It is trivially easy to blow that with code. OO with code inheritance is a prime example of combinatorial explosion that can lead to more possibilities than atoms in the universe, let alone one persons ability to reason.

Watch ‘Simple made Easy’ by Rich Hickey; a classic from our industry. The battle against complexity is ever ongoing. https://youtu.be/SxdOUGdseq4?feature=shared

brabel|6 months ago

You should not need to read every line of code in every file and function to understand what’s going on to the level you need to solve a particular problem. You must make a decision to NOT look deeper at some point on any non- trivial code base. A good program with good names and comments in the appropriate places is what allows you to do exactly that more easily . When you see sort(usernames) in the middle of a function do you need to dive into sort to be able to understand the code in that function?? Probably not, unless you are fixing a bug in how usernames are sorted!

With that said , get good at jumping into definitions, finding all implementations, then jumping back where you were. With a good IDE you can do that at the speed of thought (in IntelliJ that’s Cmb+b, Cmd+Alt+b, Cmd+[ on Mac). I only open more than one file at the same time when comparing them. Otherwise it’s much easier to jump around back and forth (you can even open another function inline if you just want to take a Quick Look, it’s Alt+Space). Don’t use the mouse to do that, things you do all the time can be made an order of magnitude faster via shortcuts. Too many developers I see struggle with that and are embarrassingly slow moving around the code base!

seadan83|6 months ago

Experience helps to recognize intent sooner. That reduces cognitive load. Getting lost 5 levels deep seemingly never stops being a thing, not just you.

tverbeure|6 months ago

Only half joking: I don’t think I trust a book from an author who has inflicted decades of TCL pain on me (and on the entire community of EDA tool users.)

RossBencina|6 months ago

I know you're only half joking, but I don't think you can pin the blame on John or TCL. Osterhaut's thesis, as I recall, was that there is real benefit to having multiple programming languages working at different levels of the domain (e.g. a scriptable system with the core written in a lower level language). Of course now this is a widespread practice in many domains (e.g. web browsers, numerical computing: matlab, numpy). It's an idea that has stood the test of time. TCL is just one way of achieving that aim, but at the time it was one of few open-source options available. I think scheme/lisp would have been the obvious alternative. AutoDesk went in that direction.

I remember using TCL in the 90s for my own projects as an embeddable command language. The main selling point was that it was a relatively widely understood scripting language with an easily embeddable off-the-shelf open source code base, perhaps one of the first of its kind (ignoring lisps.) Of course the limitations soon became clear. Only a few years later I had high hopes that Python would become a successor, but it went in a different direction and became significantly more difficult to embed in other applications than was TCL -- it just wasn't a primary use case for the core Python project. The modern TCL-equivalent is Lua, definitely a step up from TCL, but I think if EDA tools used Lua there would be plenty of hand-wringing too.

Just guessing, but I imagine that at the time TCL was adopted within EDA tools there were few alternatives. And once TCL was established it was going to be very hard to replace. Even if you ignore inertia at the EDA vendors, I can't imagine hardware engineers (or anyone with a job to do) wanting to switch languages every two to five years like some developers seem happy to do. It's a hard sell all around.

I reckon the best you can do is blame the vendors for (a) not choosing a more fit-for purpose language at the outset, which probably means Scheme, or inventing their own, (b) or not ripping the bandaid off at some point and switching to a more fit-for-purpose language. Blaming (b) is tough though, even today selecting an embedded application language is vexed: you want something that has good affordances as a language, is widely used and documented, easily embedded, and long-term stable. Almost everything I can think of fails the long term stability test (Python, JavaScript, even Lua which does not maintain backward compatibility between releases).

Shorn|6 months ago

Unsurprisingly, minimising the amount of cognitive complexity is how you get the most out of LLM coding agents. So now have a theoretically repeatable way to measure cognitive load as contextualised to software engineering.

leke|6 months ago

Yep pretty much. This could literally be notes taken from the book including the phrase itself.