Architecting your project
Going "all in" on a dependency can buy you great velocity, but at the cost of future flexibility. When evaluating dependencies, consider the tradeoffs of adding a dependency vs. the cost of creating and maintaining that functionality yourself.
Build functionality by combining smaller, specialized components instead of creating deep inheritance hierarchies. Composition offers greater flexibility and modularity which will make you move faster as things change over time.
Wait until the commonalities scream at you for abstraction so you can be in the right frame of mind to provide that abstraction. Over-abstraction obscures how a system works together.
Split code by responsibility, so unrelated concerns don’t mix. It’s a form of modularization but applies to both logic and organization. This principle requires a solid understanding of what constitutes a 'concern.' Traditionally, this was confused with 'separation of technologies' where CSS, HTML, and JS were meant to be separated. Rather things which change together should be as close to each other as reasonably possible. This reduces indirection (in some cases it can be completely eliminated) which makes evolving the codebase easier.
Ensure data and state are managed in one place and consistently accessed from that single source of truth to avoid duplications and inconsistencies. A common mistake is to synchronize state between two different components where instead this should be derived from the state in a centralized place.
Design code and interfaces so that they behave in ways that are intuitive and predictable for users and developers alike. When people interact with your software, they should feel it behaves as expected without needing to double-check or mentally adjust to unexpected quirks. Favoring immutability and more explicit APIs can help reduce surprise for users of functions you write.
Share this principle