Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Software Engineering principles to make teams better (principles.dev)
382 points by _wp_ on June 30, 2021 | hide | past | favorite | 102 comments


I think i’ve got a slightly different take - i’m not saying my take is any better though.

If we’re talking engineering principles, not team dynamics, then it’s:

  1. Use immutability by default, even in languages that make that harder than it should be
  2. Understand how liberating idempotency is
  3. Divide and conquer, use abstraction to make hard problems manageable
  4. Delete code, delete tests, re-write stuff, re-write it again. Painful? Keep doing this till you get to the other side and feel liberation and empowerment, took me years to get past wincing at the idea of re-writing that thing AGAIN
  5. Pay attention to your build tooling - to do any given task, it should always take exactly 1 command. How do i run the tests? Run the test command. How do i deploy to non-prod? Run the deploy command. Etc
If we’re talking softer stuff:

  1. Don’t block collaboration - e.g. if there are 5 people don’t work on 5 tasks concurrently. Work on 1, maybe 2 in some cases
  2. Accomodate 2 types of work: work that needs collaboration & synchronisation (brain storming, planning, why are we even doing this, that kinda thing) work that needs focus time - sometimes that’s focus as an individual, sometimes that’s a mob focussed on one task and saying no to every other distraction in the world no matter how “urgent”
  3. Change what you’re doing throughout the day, don’t try to code all day, you’ll get in a rut, you won’t write your best code after a while. You can optimise this step, e.g. if the team is low energy after lunchtime, use that to your advantage and schedule the easy boring work then, don’t be afraid to use humour or burn low energy time just getting to know each other better
  4. Make sure people are being heard in the team. Hardest point to get right.


> Use immutability by default, even in languages that make that harder than it should be

That and referential transparency are huge wins. It is a shame languages like Python make it such a challenge.


Immutability isn't so difficult to do with Python language itself for any object. The push back is culture and practice of "we are all adults here" and that influences the language to make it forcing immutability awkward.

For example, let's assign a constant variable to a sequence; should you do use list or tuple (FOO = [1,2,3] or FOO = (1,2,3) )? It doesn't matter, the constant capitalization discourages you to mutate it.


I don’t want constants. I want immutability. The problem isn’t someone reassigning FOO. It’s them changing the data that FOO represents. So I’d love to just use Python and pass around tuples, strings, frozensets and a homemade frozendict, but the performance is terrible. Any change means a full copy of the whole data structure.


> The push back is culture and practice of "we are all adults here" and that influences the language

It's strange that a general idea expressing (assuming?) overarching maturity has led to quite so many petulant arguments, wouldn't you say @ketozhang?


This was the first time I've encountered the mention of "referential transparency", so I looked it up and went down a deep rabbit hole. It seems that it's normally used to describe the property of not having side effects, however, I also found this long explanation on Stack Overflow: https://stackoverflow.com/a/9859966/58099

So now I'm more confused than when I initially read your comment. Do you mean "referential transparency" as in "expressions without side effects" or do you mean it in some other way that I don't understand yet?


It's more subtle than than "no side effects". Take Python prior to version 3, for example, which made possible the most egregious violation of referential transparency I've ever seen: `True, False = False, True`

Python code (pre-v3) that branches cannot be referentially transparent by definition because runtime context (the state of True/False bindings) is a hidden input in every boolean expression. You could have millions of lines of side effect free code and it will break completely if that one statement is run before the rest of the code.

Programmers depend on the referential transparency of keywords like "true", "false", "for", etc whether they're writing pure functional code or imperative spaghetti messes.


I just mean it in the sense of functions without side effects on data in the program (I’m not fussed about I/O).


In this particular case do you just mean pure functions then?


Sure. I’ll be honest, the distinctions are challenging to me.


It's easy to do immutability in python, just use NamedTuple for classes and tuple instead of list


Please don't do this, there are far better approaches. Use dataclasses/attrs that are frozen for immutable record types.

(Specifically avoid namedtuples unless you're hoisting a tuple to a real record. Record types also being a weird union with a tuple is...oft confusing)

For immutable basic data types, adopt type annotations and prefer to use sequence/iterable and mapping to direct didn't/list types. These provide statically verifiable immutability and also allow begin to encourage useful async-friendly patterns.


Since PEP 591 [1], if using mypy with Python 3.8+ or the typing_extensions module, you can also take advantage of typing.Final, which lets you statically verify something isn't changed. The catch is that it isn't enforced at runtime.

  [1]: https://www.python.org/dev/peps/pep-0591/


...or `dataclass(frozen=True)` if you need the ergonomics.

For non-primitive members, `frozenset` is in the standard library, but frozen dictionaries are unfortunately lacking.


I second this. My current job is the first place I've worked where the convention is to use NamedTuples for most things I've seen dicts used for on past projects and I quite like the pattern. It both makes the code more readable and avoids a number of subtle bugs that can crop up.


Sure, we'll do all that. In the mean time, could you please get started on getting the _rest_ of the community to adopt that as a well established idiom and then also have them initiate efforts to adapt popular libraries/frameworks so that they implement the adopted idioms?

Would Thursday be a good time to checkpoint on this?

Thank you so much.


I’ll research those, thanks. Any suggestions for OSS projects that do so today?


This is a good talk that demos NamedTuple: https://www.youtube.com/watch?v=wf-BqAjZb8M


Don't expect a better result after a rewrite. Be prepared to accept that.


> Don't expect a better result after a rewrite

but what's the point then? why spend effort for it, if it isn't expected to be better?


> but what's the point then?

Learning. The result may not be meaningfully better, but you gain fuller understanding of the problem domain. The third rewrite might have a chance of being an improvement ;).


You may just learned that there's no more meaningful improvement to be made. That should probably be documented somewhere after that discovery is made.

Somewhat relatedly, I've refactored processes down from 25 hours to 20 minutes. It got to 20 minutes, and other people started nitpicking that it could 'be faster' (the same people who'd let it get to 25 hours, then threw up their hands and said "it can't be fixed"). They spent loads of time getting it from 20 minutes down to... 17-18 minutes (spending 2+ weeks in the process). I'd indicated "there's really no more 'there' there - this is about as fast as it's going to get, without faster hardware". They had to find out for themselves there wasn't much more juice left in the tank.

Maybe it's not related, but I felt like sharing it anyway... :)


Meaningful improvement is in the eye of the beholder. There's the old tale of how Google found out 500ms in latency was a 1% drop in traffic. http://glinden.blogspot.com/2006/11/marissa-mayer-at-web-20....

20 to 17 minutes is a 15% time savings still.


25 hours to 20 minutes is insane. The process ran daily and only needed to run daily. 20->17 was unnecessary and a waste of time.


Can't edit my post - posted too soon. 20 min -> 17 min was unnecessary for the client, but... 2 other people got to 'share' in the credit, because 'the team' got this down from 25 hours to 17 minutes. They got to learn something, after telling me it was 'impossible', but... that wasn't enough. They needed to put their fingerprints on it, then got to take credit for something. And took it they did. Annoying as hell, 15+ years later.


Next time my boss asks why I rewrote that component for the third time instead of doing real work, I'll answer "learning". ;)

In all seriousness, from a business standpoint, it's almost never worth rewriting anything unless you're already making significant changes to that code. The opportunity cost of not spending that time building things that get you paid is just too high—not to mention all the new bugs that you're likely to introduce.


I wrote it a bit tongue in cheek, but honestly, I've recently started to consider writing the first solution as a pure learning exercise. I.e. when writing a new module/feature, I lean towards getting a solution implemented quickly, fully expecting to immediately scrap most (or all) of it, and write it again. Most of the time, this happens way before the code goes into review phase, so nobody knows I've actually written something twice.

In my experience, most of the insights cluster in two places: just as you start writing your solution, and just as you think you're done with it. So you want to get through the whole process quickly, just to learn the things you need to write a proper solution.

Related, from a different HN thread - https://news.ycombinator.com/item?id=27692710 - "action produces information".


> so nobody knows I've actually written something twice.

that's not really what a rewrite is. A rewrite is when something has already been in production, and the engineer re-do the same feature set, but refactored or rewritten in a different (possibly better, but not guaranteed) form. This rewrite takes up the time that would've been spent on something else.

If you were doing a "rewrite" during implementation, but still delivered on time, that's not a rewrite - that's just good engineering, doing exploratory coding! If you couldn't deliver on time because of the rewrite, then i guess you can call it a rewrite.


I don't think this is what people mean by a rewrite here. I expect any code that makes it to review to undergo some kind of refactoring. Sometimes it's going to be refactored as part of the review. And if throwing away your first draft works for you to create better software, that's fine! I still consider this as some kind of refactoring.

Throwing away a whole repository that was tested and running in production or on some dev environment, is something different in my point of view. And I think that's what people mean by "rewrite".


You make it sound as they are talking about full rewrites but it's actually about rewriting stuff here and there.

You are also assuming one just rewrites stuff blindly and randomly without any prior new insight.


I took that to mean refactoring the code, not throwing it away and start over.


I agree with everything except for this:

> 4. Delete code, delete tests, re-write stuff, re-write it again. Painful? Keep doing this till you get to the other side and feel liberation and empowerment, took me years to get past wincing at the idea of re-writing that thing AGAIN

With experience I've realized that rewrites are almost never a good idea[0]. They are time consuming and inevitably introduce new bugs (or reintroduce old ones), and the benefit is almost always marginal. If your architecture allows it, it's better to sandbox working legacy code and leave it as-is than constantly rewrite it.

My own policy is: if you're making significant changes to a unit of code anyway, clean it up or rewrite it; otherwise, make the smallest change necessary and leave it alone. If it ain't broke, don't fix it.

(Obligatory Joel Spolsky article on the topic that everyone has probably already read: https://www.joelonsoftware.com/2000/04/06/things-you-should-...)

[0]: ...with a possible exception if you're at a very large tech company with a lot of resources, where the testing/QA processes are a lot more thorough and it's possible to keep up with constant changes like this. But even then, seeing the amount of bugs introduced with each update to Facebook or Gsuite or any other large piece of tech, I'm skeptical.


> With experience I've realized that rewrites are almost never a good idea [...] If your architecture allows it, it's better to sandbox working legacy code and leave it as-is than constantly rewrite it.

What if your architecture is the problem? If you got the domain model wrong and you have deeply-nested complex types used throughout, you will never break free without a clean sheet rewrite. Certainly, you can refactor bits and pieces to conform to a correct model, but it's going to be an uphill battle the whole way. 10x if you are already in production with business state tied to the legacy schema(s).

Most of the horrible things I have seen at code review time have a root cause somewhere in poor domain modeling.

For example: Someone put support for 2 customer addresses as facts directly in the Customer type, so now you can't deal with the new edge case of 5+ addresses per customer, or model the idea that the address might be shared between more than 1 customer (and/or some other business types).

If you didn't model for 3NF/BCNF/DKNF up front, you might as well start over from the beginning in my experience. If your problem domain is not that complex, you can probably survive with something really shitty, but the moment you enter into 50+ types, 1000+ facts and 100+ relations, things are impossible to manage without strong discipline in this area.


I might, grudgingly, support this kind of rewrite, if it was proposed with a domain model that can already support all existing data. If it's proposed with "a new domain model would be so much nicer! We could do it right!" then hell no.

I'd still prefer to do it incrementally if possible.


I don’t disagree in principle, but I have also never encountered architectural problems so deep that it was impossible to solve them piecemeal. I’m sure they exist, and in those cases I’d say a rewrite is probably worth it in the long run, but I haven’t seen it in practice.


My thinking definitely aligns with yours on this:

> if you're making significant changes to a unit of code anyway, clean it up or rewrite it; otherwise, make the smallest change necessary and leave it alone. If it ain't broke, don't fix it.

Don't be afraid to rewrite something when it needs it, and build knowing it's very possible you'll rewrite later.

If there are good unit tests, and it's a sufficiently small/decoupled piece of code, then rewriting is not so bad. These are all self-reinforcing things: small, decoupled code tends to be easy to rewrite; Testable code tends to be decoupled.

Systems always get more complex over time. The key thing is figuring out when your simple component is starting on the path to getting too complex, and taking the time to rewrite it as early as possible -- maybe it should be two smaller components, or maybe the entire organization of that are needs to be different. If you wait, and just keep adding "small" things, eventually you have a monster that's an order of magnitude more complex to deal with.

The other bit of this is writing code knowing you can (and may likely) rewrite it later. If you try to predict the future complexity early in design, 9 times out of 10 you will get it wrong, and you'll end up in a lose-lose situation: you have a overly-complex component to deal with, and it ends up needing a rewrite later anyway. Rewriting this is even harder because you have to undo the unnecessary complexity. This is also known as YAGNI ("You Aren't Gonna Need It").


> If there are good unit tests, and it's a sufficiently small/decoupled piece of code, then rewriting is not so bad. These are all self-reinforcing things: small, decoupled code tends to be easy to rewrite; Testable code tends to be decoupled.

The problem with this mentality is that everyone thinks they write “good unit tests”, but bugs are still found in production. :) You can’t use unit tests as justification for software being reliable; being battle tested in the real world is a much better indicator in practice.

I mostly agree with your other points, although I would still advocate doing these rewrites in as small of pieces as possible, in a way that’s as backwards compatible as possible, instead of all at once. I think you may be saying roughly the same thing, but this thread has shown that people have different definitions of “rewrite”, so it’s hard to tell. :)


> You can’t use unit tests as justification for software being reliable; being battle tested in the real world is a much better indicator in practice.

My point on unit tests to support a rewrite is that it can catch a lot of edge cases, and you can remain reasonably certain you didn't break things if tests are still passing when you're done.

But otherwise I totally agree -- you can have as many tests as you want, with whatever coverage numbers you want, and it will still fall down in the real world. I don't focus on coverage or being dogmatic about TDD. I like to heavily unit test algorithms/regexes/etc -- anything with a defined input and output. At the same time, I hate testing 'glue' code (like an MVC controller), and would way prefer to rewrite it to be so simple it either works or it doesn't work at all (causing a big, obvious failure).

As bugs are found in the real world, in the ideal case write a test which basically guarantees that bug never happens again. Not always possible or takes such a massive effort that it isn't worth it, but usually it pays off.

> I would still advocate doing these rewrites in as small of pieces as possible, in a way that’s as backwards compatible as possible, instead of all at once.

Yes, exactly, that's what I was trying to get at. Big rewrites are hard to get approval/agreement to start, hard to actually do and often unsuccessful. Worse, a team with the mindset "ah, it's okay if we cut corners here, we're going to rewrite this whole thing someday" will rack up a serious amount of technical debt -- and make that big rewrite even harder. Ironically this makes a big rewrite even harder to start and succeed, but also more necessary.

Small continuous rewrites are a way of constantly improving quality, while avoiding a lot of these traps.


Imagine if we made bridges this way... Lets just knock it down and rebuild. Who cares about cost or traffic.

In some ways software engineering is so immature compared with other engineering disciplines.

I believe more in boy scout - "if you touch it leave it better than when you arrived".

:-)


I really like that you've thought about these - and feel free to submit them not only will you get a founding badge but if you're the first person to create it you'll be known as the source of them in the future.

It's not really about better or worse, what I've found is different people have different backgrounds and what principles they find useful is based on several things, which may be immuntable - such as strongly held values.

You're never going to make me not care about aesthetics for example, as that is intrinsic to me and that will affect the principles I like and ultimately the people that I work best with.

An analogy I like to think about is that people with very different principles are like two people holding a rope and pulling away from each other - you aren't going to get anywhere fast.

Whereas people with similish principles, will generally go in the right direction. Sure, they'll get tangled up from time to time and you won't always want to get in exactly the same way. But there's a collaborative nature to it and you'll both improve.


> feel free to submit them not only will you get a founding badge but if you're the first person to create it you'll be known as the source of them in the future.

That sounds wrong. As in "I could upload quotes from Gang Of Four books and claim I am the source" wrong.


A lot of thought has gone into the licensing. Hopefully I've covered all bases.

You can't be an author if you aren't the author of a principle or the principle is too generic. If the principle is already open source (e.g. on wikipedia, has a creative commons license) you can submit it but not claim you are the author for it and submit it under the same licensing terms (CC-BY-SA) as long as it doesn't break the license.

Codifying the principle for the first time takes effort and people can iterate on it to make it better over time. Many people may have had similar thoughts before, but if it's not a general principle already being used the first to turn it into a principle - to put a stake in the ground - benefits everyone and can help improve everyone's capability.

I believe the author should be rewarded for that effort, as long as it is their own unique work.


This sea a lot more work for no benefit, compared to just writing a (hypertext) book and citing sources.


The eventual benefit is having access to many community sourced principles as a resource, which are getting better over time as people contribute.

Then being able to create your own lists for unique situations. Say "Lupire's CTO list" or "Lupire's management principles" and to share that with your team or as a reminder to yourself.

Of course you should always be able to export it and put it in a format that's useful to you. And that's been really important to the design. From using markdown format to embedding license information and meta data with the principle, it should help a lot with portability.


> 1. Use immutability by default, even in languages that make that harder than it should be

What I've come to realize is that whenever people talk about wanting immutability, they really want value semantics. Value semantics can be achieved without immutability - the best example is Swift's structs. They can be both mutable or immutable, but they can never be shared, i.e. there can never be more than one reference to a single struct value. This means that modifying the value has no effect on other references, because there can be no other references.

This makes whether or not the value is immutable irrelevant, or at least not necessary. You get the same benefits - you can reason about your code locally and not worry about your changes modifying unknown parts of the program. Local reasoning is the end goal, and value semantics is the mechanism of getting to that goal. Immutability is only one way of getting value semantics.


Can you explain in more detail? I agree that local reasoning is the end goal, but as soon as you change things (i.e. mutate) then it doesn't work anymore. Or in other words: if modifying a value has no effect on anything else, then why would you modify it? I don't get it - maybe a code example would help.


An example might be a function that takes a vector3, adds 5 to the x value, then returns the vector's length. You could model that as "make a second vector v_2 {x = v.x + 5, y = v.y, z = v.z}; return v_2.length", but if you have struct semantics, you can just do "v.x += 5; return v.length", and be confident that you're not modifying the vector that the caller has.


I see - but then you would still not have guaranteed local reasoning within the part of the code that modifies the vector.

Is that really worth it?

Also, creating the second vector should really look like "v2 = v.copy(x = x + 5); return v2.length". Or even just "return v.copy(x = x + 5).length".


You showed why it is worth it - it also avoids copying. Mutation is more efficient in every way, except cognitive overhead. So by preventing _sharing_ of mutable values, you get the best of both worlds.

This is the same model that Rust takes too. It doesn't eliminate mutability, only controls it. The argument that "performance doesn't matter anymore because computers are so fast" is a bad one. Efficiency is efficiency, and immutability will always be less efficient.


It's certainly fair to balance/decide between performance and local reasoning. But one should be clear then that they give up one for the other and not claim that both is possible. Because from my understanding from what you said, local reasoning isn't possible anymore, even though reasoning is still easier compared to when you don't have struct semantics. But that's still two different things.


You still have local reasoning with struct semantics. You are not giving that up in exchange for performance, you get both.

  func newStructValue(s: Struct) -> Struct {
    s.value = 5

    return s
  }
The reasoning for this block of code is totally local to the function body. Because the only reference to `s` is in the body of the function - it cannot be more local than that.


Everything is great there, and in particular number 2 is awesome and 5 is FTW !

Also, don't let people guess what are those commands - create a menu that is run as soon as you clone the repo and shows all commands. I now use Invoke-Build [1] system for that and on all projects you type ib ? to get that list and later ib <task> to execute it (or any combination of it, i.e. ib DropDatabase, Run, Test). Basic tasks are named the same on all projects no matter the underlying technology.

Later you use the same command on CI server - its just another developer that runs ib Build, Test, Publish. Any more programming in CI yaml files then 1 to 3 lines per job is so wrong.

[1] https://github.com/nightroman/Invoke-Build


> 2. Understand how liberating idempotency is

Can you give some examples?


Say you have a batch job that performs tasks A–E, and each of those tasks might process thousands of records. At some point, something will go wrong that causes the process to crash, hang, or error over many records. If the code is not idempotent, you need to investigate exactly where things started going wrong and figure out how to resume the process at that point. You don't want to reprocess records and e.g. send out duplicate emails or double-increment some number you're tracking. If the code is idempotent, conversely, you can just start the whole process over again without having to worry about any of that.

Similarly, many systems involve consuming from some message queue. It's basically impossible to guarantee exactly-once delivery in most systems. You either have to risk missing a message, or having it delivered multiple times. If you're running idempotent code, you can always err on the side of redundant delivery without any ill effects.


Last week I ordered a screen protector for my phone. I got two boxes in the mail. I thought I ordered twice by mistake but they had the same order number on the packing slip.

My immediate thought was that some order processing step somewhere is not idempotent.


How do you make something idempotent if one of the effects is sending an email?


You can't make everything idempotent.

You should still try and make most things idempotent.


Side effects go to the border of the app. You keep track if for some message X you have sended the email, so you garantie that you send it once.


Idempotecy means you can run something several times and it will do the same thing.

Let's say you're controlling a factory and you have a function that fills a reservoir. A naive way would be to define the semantics of the operation as "send enough liquid" and open the circuit for 10 minutes when activated. Since it takes 10 min to fill, all is good.

But what if your program crashes and you have to rerun. If the reservoir was already semi-filled, you will overflow it.

If the semantics of the operation is "send liquid until 'full' sensor indicates you're done", that's liberating. You no longer have to worry about overflowing the reservoir.


And is another way to say this "side-effect free"?


The Linux shell command 'touch foo' is idempotent, but not side-effect free.

Reading a variable shared between threads without a lock is side-effect free but not idempotent.


Interestingly, `touch` is not idempotent because it modifies timestamps. Not being pedantic, just an interesting consideration. `mkdir -p` is idempotent, I believe.


You're absolutely correct, it's not idempotent for the crucial reason you mention. It's crucial because updating the timestamps is the main purpose of 'touch' in the first place!


Reading a variable isn't side-effect free either :P


In the sense that you're moving data in to a register and updating the program counter?


And potentially changing the contents of processor caches.


Opening Schrodinger's box isn't side-effect free. :)


I like a lot of these principles but there is a crucial element missing from this presentation and that is time.

As a company grows the importance of each of these principles changes. As a project within an established company grows a similar maturation happens.

If you tried to adhere to all 'best practices' (principles or not) from day 1 of a project you'd be carrying a lot of weight that could crush otherwise good ideas.

Some principles, when presented without a timeline, seem contradictory and that can lead to fights on teams where developers are well meaning but less experienced.

I'm encouraged that this presentation includes dimensions along which you can slice the principles, but I encourage the authors to adopt a "stage" dimension as well that helps focus devs/learners on principles appropriate for 1. The stage they are in and 2. The immediate next stage.


The temporal nature of principles - It's a good suggestion. It's always hard to slice reality.

Teams should come up with their own list of principles which reflect the team and adopt organizational ones that make sense. As organization ones will tend to be wider in scope and less prescriptive, it should be less of a problem to adopt them.

Basically teams should decide what's right for them based on their backgrounds and the organisation should provide general direction.

The principles become more useful when they are context sensitive, so principles lists are the next big feature (i.e one for your team, another for your organization)

This feedback is really useful and will inform the design of the app. If you have a good idea Ian of what you think is useful, please let me know how I can help. I think I've grokked the basics of what you're saying, but it would be good to lock it down.


Oh wow, author here - I wasn't quite expecting this to appear here today. I've only really just start talking to people about it.

That said, my mission is to make software engineering better for everyone. By capturing the best principles from the best in the world at what they do. And to organise and share that knowledge, freely.

It's currently missing features to organise principles well, but I'm working on it (discussion here: https://github.com/PrinciplesDotDev/principles/discussions/2...)

You can reach me through my profile or following me on @princples_dev (twitter) - I've only starting pushing this, but a lot more will be coming soon.

It's an open source project and it needs your principles to make this a reality so if you've got some to share, please do. Or if you have feedback, leave it here. I'm building it for you.


Is there one page where all principles and information about them are collected as one page so it can be downloaded and read in ereader?


All principles on the website are currently in this repository: https://github.com/PrinciplesDotDev/principles In fact, this is the database for the website.

So you can download them from there and put it in an ereader or import it into an application that supports .md format.


Is it just me, but is this meme version of software engineering absolutely the lowest information density achievable? Seriously. This is a heavily-involved topic, where any project might require hundreds to thousands of hours of development, and we have five second soundbytes as principles. Ugh.


In software engineering, the smallest behaviors interact to cause more complex ones.

In most teams, it's hard to point to those small behaviors because they have become habitual and you may have forgotten what they are. Forgotten the "why".

What I see in teams that don't work well, is teams don't have that guidance or don't share similar mental models to allow them to work effectively together. So you end up arguing at a higher level than the actual problem because you can't put your finger on what you believe.

Principles build capability. Take Redux, it has just three core principles: https://redux.js.org/understanding/thinking-in-redux/three-p... and from those core principles you can almost build the whole framework.

But more importantly it provides capability and confidence to people using the framework to extend if needed in a "redux" way. Instead of looking at the documentation, they know the authors intent. The "Why"

That said, the site is really in it's earlist phase and the principles will be improved over time with more in depth information as more people contribute. Eventually, a voting system will be in place so the best principles will come to the top.


If you have ever heard any sports announcer/commentator go on and on about "the fundamentals" - well, here you go. As he points out little things quickly add up into big things. Don't take my word for it - search out just about any interview with a successful athlete about their career and I guarantee at least some, if not a significant chunk of it will dwell on the topic of "fundamentals".


It'd be cool if counter-points to a principle could also be documented alongside it. Reasoning that highlights why you may not want to adopt a principle is helpful when considering it.

For example, take "One single source of truth".

>Data should be held in one location, duplicates of that data should be by reference only.

>Why

>Changes to data are always propagated to the rest of the system.

>Mutations to the data need only happen in one place.

>Single source of truth means no data will be out of sync or fail to be updated.

>How

>Only allow data writes to happen in one location. Whether that be a call to a rest API, system call or other write actions.

>Don't allow data to be stored anywhere but the single source of truth.

That won't work in many situations. If you need to support high throughput OLTP use-cases but also want to support advanced filtering/sorting of large datasets, you may need something like a combination of a NoSQL DB with something like ElasticSearch. Eventual consistency is something you accept as a trade-off for enabling higher levels of performance.


It's funny you chose that principle in particular because it does have an exception listed:

https://principles.dev/p/one-single-source-of-truth/

> Exceptions > Highly distributed systems - Some systems rely on data consistency to be reached eventually or may never need to have accurate data.

I've written about exceptions here: https://principles.dev/documentation/#exceptions-optional

In general, exceptions should be quite broad and people can add these to the principles if they know of them.

They can also have higher priority "contradictory principles" which override the lower priority principles in certain cases.

Overlapping of principles create complex behavior so it is usually better to have a list of principles in priority order (this will be covered under emergent behavior in "principle-driven engineering") which can override the "single source of truth" principle.

An example might be: Engineers will know they should use single-source of truth, but as performance should be critical (or they have a specific business rule that states something must take less than 5ms) it will override that principle.

Does that answer your question or do you think it needs to be more refined than that?

I wonder if making the principles editable by the team would be a useful feature, so you'd be able to add your own exceptions to them for your particular use case.


Haha, that's an interesting coincidence. I initially clicked into a few that didn't have exceptions listed and just picked that one when writing my comment. I just copied/pasted the beginning and didn't expect the extra section there.

Being able to stack rank principles is interesting. I think in practice, different principles apply to different use-cases and systems. There may be a certain set of principles for "critical" functionality pieces, but maybe a different set for features like reporting (such as staleness of data).

Amazon's leadership principles aren't specific to engineering, but they keep competing ones with tension between them all the same (e.g., "Bias for Action" vs "Insist on the Highest Standards").


It's the main feature that's missing from this - I'm working on it - context sensitive principle lists, with ranking, which can slice up reality in the way you've said.

Going to be a hard one to get right, so I'm taking my with that one.


I'm pretty excited about this site's potential. I am avid promoter of creating autonomous teams through principles and goals alignment (OKR).

I think there were suggestions about Time being an aspect of a principle. I wonder if this would be better represented by the SDLC stages, something like this might address the "time" part that allows it to be associated with type of work being done. Just a thought. I also wonder, at the same time, if this is too burdensome for a team or individual to do. Establishing your principles, IMHO, should be the most important thing you do, if you use them to guide your behavior.

I also have hope that when people establish what their individual principles (that define them) they then can ensure when they are job hunting or self reflecting on their current job that they what they are doing is aligned with who they are to ensure they will be satisfied in that role/job/company.

I just bookmarked the site. Excited to see this grow.


Oneshoe, thank you.

On the SDLC, it could work. The temporal aspect is something I need to think through in a way that's not too complicated. Getting feedback at this stage is beneficial, even if this hit HN a lot earlier than I was expecting.

Establishing your own principles is burdensome, but using others is not. Having access to everyone else's principles and being able to see what other successful teams use, makes it easy to take other people's capability and add it to your own. Imagine if you could see what principles Rob Pike or <insert favorite programmer uses> or the principles behind a library, framework or a particularly productive team? This gets me excited. It gives people the building blocks to make great things.

I totally agree that individual principles is extremely important. If not for the very fact that finding and being on aligned teams is an amazing experience for everyone involved. Happier, more productive teams.

I would love to have an informal chat with you, you get what I'm doing and it needs people like you to for this to succeed for the community. Drop me an email if you can take me up on the offer :)


> Backend ... no principles found

Site checks out

https://imgur.com/a/orYiokW


Haha! It won't be long until there are some there.

I may take out the filters by technology choice for the time being, because there's nothing there.


I once worked at a startup which had put together a binder of documents, declaring the company's vision, values, principles, etc. They strongly suggested new hires go through it at least briefly.

I don't think anyone ever did.


Yeah, I know what you mean.

I've a few principles for that: https://principles.dev/p/documentation-should-be-close-to-th... and “They Ain’t Gonna Read It” (not on the website, yet. But it is here: https://blog.nuclino.com/brown-m-ms-or-why-no-one-s-reading-...)

The trouble with the documents you've mentioned is they don't really create capability and it's really the social structure that enforces those values and principles as opposed to the documents.

With engineering it's different because it provides tangible value.

From a team perspective it can help you transfer mental models. Programming is an abstract activity that benefits greatly from those shared models. They build capability, help people learn rapidly, settle disagreements, bring the team together as one and are used in things like code reviews and filtering of technical decisions. People come back to them again and again - it's integrated. Then when a new member of the team comes a long, you're not going back to those discussions again and again.

As an individual. One of the reasons you look back at your principles to remind why you believe something or to be more convincing. They provide value, so they keep being used. It's also part of that persons identity. It defines what they care about and helps them join teams with people who are aligned.

So I agree to an extent - They Ain’t Gonna Read It... Unless it provides value.


I think it would be interesting to read case studies of different teams, their principles, how those decisions have impacted them, benefits, drawbacks from their approach, etc.

I think principles are great, but like you yourself have said, they require context. I would especially be a valuable resource for junior and mid career engineers, of which I am one.


I imagine teams will shared their principle lists on blog posts and put reflections there.

Not all of it would appear on the principles.dev, as I think the reflective nature would be best handled elsewhere. But acknowledging pros and cons on the website is very valuable.

The next big piece of work I have to do is on principle lists ( https://github.com/PrinciplesDotDev/principles/discussions/2...) and figuring out what features to include and where to draw the line is going to be tricky... I need to find the principles behind it, really.

It's interesting that you say they would be a valuable resource for a junior and mid-career engineers. I agree, it would. What I've found is it generally attracts people who are a) leaders (in some form or other) b) care about programming deeply.


What does "Iterate in Thens" mean?


Well to quote the "what" of the principle https://principles.dev/p/iterate-in-thens/

You should iterate sequentially on a focused chunk of work at a time. Once that chunk has been completed THEN start on the next chunk of work. This is opposed to doing multiple chunks of different of work in parallel.

It's really a way of working as opposed to operating on code.


Click on it and find out?


Give it some time and it will end up being "The 5 Pillars of the AWS Well-Architected Framework" or something similar.

Someone already mentioned that "time" is absent here. Hence you cannot generalize. Each company should probably write their own principles and make it part of their identity.


Indeed. That is definitely the next steps to allow individuals and eventuall companies to create their own principle lists.

There are no principles that make sense in every situation, there are no teams that will have the same principles.

Imagine being able to have a team and then adopt those principles easily into your team? That's the goal.


> Note: This visualization was designed for screens larger than 1024 x 1024 and for desktop-style interactions. You can proceed if you'd like.

Why would you put in the work to warn people they'll have a bad time instead of just fixing the bad time.


Are you asking: "Why would you do less work when you could do more work?" -- not meaning to be trite I just think the answer follows directly from your question -- its much less work to say "I haven't optimized or even evaluated this for mobile" than to optimize a site for mobile, particularly if there is some important interaction you want which is easily achievable on a desktop sized screen (like drag and drop columns for instance).


Can you show me where that is or what visualization that is?

I haven't put anything on the website intentionally, perhaps it's auto generated.

The only page that I haven't purposely optimised for mobile is the editor, as you can't really create principles on your mobile.


Their comment is on the wrong post. I think they meant to comment on this post: https://news.ycombinator.com/item?id=27689664


well hell, yep. I don't understand how this comment got in the wrong thread! Thank you for pointing that out.


Cool! Similar project I maintain: https://programming.protips.wiki/


This is a great initiative. It would be great if across each principle, we can have examples on how this principle helps to improve the code quality


Some principles do have this already, it depends on the princple. They tend to be more code focused, such as compute properties when possible: https://principles.dev/p/compute-properties-when-possible/

It would be hard to do it for every case, as principles interact together to create more complex behaviors. I'll be discussing this more in "emergent behaviors" in "principle-driven engineering" at some point. But the essence is architecture can arise from a few principles together. It's a bottom up approach to architecture where team members understand the "why" so there are shared mental models between the team.


link seems to be down

slashdot(hn) effect at work ?


1. work as a team




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: