{"id":30668088,"url":"https://github.com/stanislaw/notes_on_software_work","last_synced_at":"2025-08-31T23:08:40.985Z","repository":{"id":143172714,"uuid":"48197196","full_name":"stanislaw/notes_on_software_work","owner":"stanislaw","description":"Notes from my work as a software engineer.","archived":false,"fork":false,"pushed_at":"2025-08-30T15:23:40.000Z","size":327,"stargazers_count":129,"open_issues_count":1,"forks_count":12,"subscribers_count":13,"default_branch":"main","last_synced_at":"2025-08-30T17:29:42.825Z","etag":null,"topics":["complexity","heuristics","software-design"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/stanislaw.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2015-12-17T20:40:45.000Z","updated_at":"2025-08-30T15:24:36.000Z","dependencies_parsed_at":"2025-01-26T19:21:41.838Z","dependency_job_id":"c4fe48b1-d553-4029-8e2d-e01e4b99f7b7","html_url":"https://github.com/stanislaw/notes_on_software_work","commit_stats":null,"previous_names":["stanislaw/notes_on_soft_skills_behind_software","stanislaw/notes_on_software_systems_engineering","stanislaw/softwaredesignheuristics","stanislaw/notes_on_software_work"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/stanislaw/notes_on_software_work","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stanislaw%2Fnotes_on_software_work","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stanislaw%2Fnotes_on_software_work/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stanislaw%2Fnotes_on_software_work/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stanislaw%2Fnotes_on_software_work/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stanislaw","download_url":"https://codeload.github.com/stanislaw/notes_on_software_work/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stanislaw%2Fnotes_on_software_work/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273051827,"owners_count":25037077,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-31T02:00:09.071Z","response_time":79,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["complexity","heuristics","software-design"],"created_at":"2025-08-31T23:08:38.632Z","updated_at":"2025-08-31T23:08:40.968Z","avatar_url":"https://github.com/stanislaw.png","language":"Python","readme":"# Notes on Software Work\n\nThis repository contains a collection of notes and lessons learned that I have gathered over time during my work as a software engineer.\n\nThese notes are not strict instructions to be followed; rather, they serve as soft guidelines and recommendations. They are most valuable when considered as a whole. Taken individually, some notes may even appear to contradict one another. Following them too rigidly can diminish their usefulness or, in some cases, lead to negative outcomes. There may also be some overlap between notes, so it’s important not to interpret them too literally.\n\nMost of the notes are my own observations, though I occasionally quote from influential books and other resources. All notes with quotations include references to their sources.\n\nThis repository is currently a draft, far from complete, and organized arbitrarily. Please do not expect it to be polished.\n\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n\n\n- [Day-to-Day Work](#day-to-day-work)\n  - [Leave Work Better: Improving Today for a Simpler Tomorrow](#leave-work-better-improving-today-for-a-simpler-tomorrow)\n  - [Fast Feedback](#fast-feedback)\n  - [Start Simple](#start-simple)\n  - [Break Down Work](#break-down-work)\n  - [Look Outside Your Immediate Task, Maintain the Bigger Picture](#look-outside-your-immediate-task-maintain-the-bigger-picture)\n  - [Avoid Work That Can Be Avoided](#avoid-work-that-can-be-avoided)\n  - [Understand and Respect the Customer](#understand-and-respect-the-customer)\n  - [Choose Where to Innovate (Carefully)](#choose-where-to-innovate-carefully)\n  - [Automate everything](#automate-everything)\n  - [Quick exploration](#quick-exploration)\n  - [Stay Curious and Explore](#stay-curious-and-explore)\n  - [Task Sequencing: Group Related Activities for Efficiency](#task-sequencing-group-related-activities-for-efficiency)\n  - [Plant the Tasks, Let the Mind Work](#plant-the-tasks-let-the-mind-work)\n  - [Strive for Clarity](#strive-for-clarity)\n  - [Everything Explicit. No Magic.](#everything-explicit-no-magic)\n  - [Disown Your Work by Sharing It with the Team](#disown-your-work-by-sharing-it-with-the-team)\n  - [Learn from Lessons](#learn-from-lessons)\n  - [Use Diagrams](#use-diagrams)\n- [Teamwork and Communication](#teamwork-and-communication)\n  - [Agile Software Development Requires Strong Social Network](#agile-software-development-requires-strong-social-network)\n  - [Sending Status Updates to the Team](#sending-status-updates-to-the-team)\n  - [Close the loops, acknowledge communication](#close-the-loops-acknowledge-communication)\n  - [Keep Everyone in the Loop](#keep-everyone-in-the-loop)\n  - [Leverage Early Expert Review and Broad Feedback](#leverage-early-expert-review-and-broad-feedback)\n  - [Talk in Person When You Can](#talk-in-person-when-you-can)\n  - [Recognize the ideas and achievements of your colleagues](#recognize-the-ideas-and-achievements-of-your-colleagues)\n  - [Praise Good Work](#praise-good-work)\n  - [Professional content](#professional-content)\n  - [Loop in Experts for Important Actions](#loop-in-experts-for-important-actions)\n  - [Share What Works](#share-what-works)\n  - [Share and Improve Team Workflows](#share-and-improve-team-workflows)\n  - [Keep Team Knowledge Alive](#keep-team-knowledge-alive)\n- [Complexity and Cognitive Load](#complexity-and-cognitive-load)\n  - [Solving Right Problems](#solving-right-problems)\n  - [Solutions are Context-Driven](#solutions-are-context-driven)\n  - [Weakest link](#weakest-link)\n  - [Point of View](#point-of-view)\n  - [Periphery](#periphery)\n  - [Rational and Unconscious](#rational-and-unconscious)\n  - [Engineering as Input/Output: The Role of Prepared Inputs](#engineering-as-inputoutput-the-role-of-prepared-inputs)\n  - [Humans are not designed for Big Numbers](#humans-are-not-designed-for-big-numbers)\n  - [There is no such thing as Many](#there-is-no-such-thing-as-many)\n  - [0-1-2-Many I](#0-1-2-many-i)\n  - [0-1-2-Many II](#0-1-2-many-ii)\n  - [Masking (Shadowing)](#masking-shadowing)\n- [Design](#design)\n  - [Functional analysis and decomposition](#functional-analysis-and-decomposition)\n  - [Poor Abstraction](#poor-abstraction)\n  - [Cost of Abstraction](#cost-of-abstraction)\n  - [Habitability](#habitability)\n  - [Hard Feature](#hard-feature)\n  - [True Name](#true-name)\n  - [One Pattern per Class](#one-pattern-per-class)\n  - [Archetype](#archetype)\n  - [Prima Materia](#prima-materia)\n  - [Mature automation](#mature-automation)\n  - [\"Magic\" is automation that is not adequate](#magic-is-automation-that-is-not-adequate)\n  - [Poisonous Systems](#poisonous-systems)\n  - [Bad Design in House](#bad-design-in-house)\n  - [Trade-off of Encapsulation](#trade-off-of-encapsulation)\n  - [Unnecessary Flexibility](#unnecessary-flexibility)\n  - [Black Box with a Green Play Button](#black-box-with-a-green-play-button)\n  - [Single Source Concept and Its Exceptions](#single-source-concept-and-its-exceptions)\n  - [Resilience to Change vs Fixed Perfect Solutions](#resilience-to-change-vs-fixed-perfect-solutions)\n  - [Two Almost Identical Entities](#two-almost-identical-entities)\n  - [Control](#control)\n    - [Observable Control](#observable-control)\n    - [Humans should dominate machines](#humans-should-dominate-machines)\n    - [Overlapping control](#overlapping-control)\n    - [Broken control loops](#broken-control-loops)\n  - [Feedback](#feedback)\n    - [Broken feedback loops](#broken-feedback-loops)\n  - [Separation / partitioning](#separation--partitioning)\n  - [Grouping](#grouping)\n  - [Observability vs Correctness](#observability-vs-correctness)\n  - [Don't Use RAII on a Business Logic Level](#dont-use-raii-on-a-business-logic-level)\n  - [Rich Collection of Models and Diagrams](#rich-collection-of-models-and-diagrams)\n  - [The Limits and Choices of Models and Diagrams](#the-limits-and-choices-of-models-and-diagrams)\n  - [Pseudocode as a Modeling Tool](#pseudocode-as-a-modeling-tool)\n- [Coding, code reviews, and maintenance programming](#coding-code-reviews-and-maintenance-programming)\n  - [Code that Works](#code-that-works)\n  - [Code Is Not Your Partner](#code-is-not-your-partner)\n  - [Two Strategies for Replacing a Feature](#two-strategies-for-replacing-a-feature)\n  - [Smallest Scope](#smallest-scope)\n  - [Code Style as a Blocker](#code-style-as-a-blocker)\n  - [Simplifying Complex Feature Branches](#simplifying-complex-feature-branches)\n  - [The Moving and Changing Anti-pattern](#the-moving-and-changing-anti-pattern)\n  - [Avoid Plural Names For Classes](#avoid-plural-names-for-classes)\n  - [Fast Programming and Slow Programming](#fast-programming-and-slow-programming)\n  - [Stable Components](#stable-components)\n  - [Boring Code](#boring-code)\n  - [Boring Code 2](#boring-code-2)\n  - [Lack of Knowledge](#lack-of-knowledge)\n  - [Lack of Knowledge II](#lack-of-knowledge-ii)\n  - [Goodwill vs Pain](#goodwill-vs-pain)\n- [Biases](#biases)\n  - [If It Works, Then It Works Bias](#if-it-works-then-it-works-bias)\n  - [Focusing only on what's most visible bias](#focusing-only-on-whats-most-visible-bias)\n  - [The Fix Bias](#the-fix-bias)\n  - [Resolving Merge Conflict Bias](#resolving-merge-conflict-bias)\n- [Reliability](#reliability)\n  - [Errors are not ok](#errors-are-not-ok)\n  - [Errors must be understood and described](#errors-must-be-understood-and-described)\n  - [Underlying errors shall not be hidden](#underlying-errors-shall-not-be-hidden)\n  - [Critical errors vs non-critical errors](#critical-errors-vs-non-critical-errors)\n  - [Assertions are better than no error handling](#assertions-are-better-than-no-error-handling)\n  - [Assertions are shortcuts for a proper error handling](#assertions-are-shortcuts-for-a-proper-error-handling)\n  - [Crash Early](#crash-early)\n- [Testing](#testing)\n  - [Write Tests, Even Bad Ones](#write-tests-even-bad-ones)\n  - [TDD as a Toolbox](#tdd-as-a-toolbox)\n  - [Legacy Code is Code Without Tests](#legacy-code-is-code-without-tests)\n  - [Testing as a Way to Manage Complexity](#testing-as-a-way-to-manage-complexity)\n  - [Test It to Engineer It](#test-it-to-engineer-it)\n  - [Improve Testability](#improve-testability)\n- [Distribution](#distribution)\n  - [Provide Basic Test Sequences with Your Product](#provide-basic-test-sequences-with-your-product)\n  - [Provide Drivers Alongside Your Hardware](#provide-drivers-alongside-your-hardware)\n  - [Provide Simulators Alongside Your Hardware](#provide-simulators-alongside-your-hardware)\n- [Documentation](#documentation)\n  - [The Illusion of Easy Documentation](#the-illusion-of-easy-documentation)\n  - [Software Design Document](#software-design-document)\n  - [Less prose, more structure](#less-prose-more-structure)\n  - [Too Much Structure Overload](#too-much-structure-overload)\n  - [Encyclopedic Document](#encyclopedic-document)\n- [Meetings](#meetings)\n  - [Sound Check](#sound-check)\n  - [Meeting Agenda](#meeting-agenda)\n  - [Meeting Notes](#meeting-notes)\n  - [Capturing Meeting Results](#capturing-meeting-results)\n  - [Briefing In](#briefing-in)\n  - [Sharing Screen \u0026 Presenting Material](#sharing-screen--presenting-material)\n- [Systems](#systems)\n  - [Good enough is often best](#good-enough-is-often-best)\n  - [Designing Systems for Effective Work](#designing-systems-for-effective-work)\n  - [The Risk of Default Outcomes](#the-risk-of-default-outcomes)\n- [People and Organizations](#people-and-organizations)\n  - [Everyone is busy](#everyone-is-busy)\n  - [Solving Problems with Cash](#solving-problems-with-cash)\n  - [The Paradox of Rushing in Software/Systems Engineering](#the-paradox-of-rushing-in-softwaresystems-engineering)\n  - [Four seasons](#four-seasons)\n- [Standards](#standards)\n  - [Idealized standards vs. practical implementation](#idealized-standards-vs-practical-implementation)\n  - [The challenge of standards implementation](#the-challenge-of-standards-implementation)\n  - [Standards and best practices](#standards-and-best-practices)\n  - [Standards favor good practice](#standards-favor-good-practice)\n  - [Wrong is worse than early or incomplete](#wrong-is-worse-than-early-or-incomplete)\n- [Requirements](#requirements)\n  - [One-stop shopping](#one-stop-shopping)\n- [Safety](#safety)\n  - [Safety does not exist without blood, loss or failure](#safety-does-not-exist-without-blood-loss-or-failure)\n  - [Safety is boring](#safety-is-boring)\n  - [Safety is very hard to achieve but is very easy to lose](#safety-is-very-hard-to-achieve-but-is-very-easy-to-lose)\n  - [Success breeds failure](#success-breeds-failure)\n  - [Safety as a Defensive Discipline](#safety-as-a-defensive-discipline)\n  - [Safety for Engineering is Like Medicine for People](#safety-for-engineering-is-like-medicine-for-people)\n  - [User Interfaces and Critical Systems](#user-interfaces-and-critical-systems)\n- [Books](#books)\n- [Similar resources](#similar-resources)\n- [Copyright](#copyright)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\n## Day-to-Day Work\n\n### Leave Work Better: Improving Today for a Simpler Tomorrow\n\nAlways leave the work artifacts - whether code, documentation, diagrams, models,\nor others - in a better state than they were before, giving future you or\nsomeone else the opportunity to improve them even further.\n\n### Fast Feedback\n\nFast feedback is essential for making progress and avoiding wasted effort. It\nhelps engineers quickly test ideas, catch mistakes early, and stay in the flow.\nUseful ways to get fast feedback include test-driven development, fast-running\ntest suites, effective debugging tools, and simply asking a colleague for quick\nadvice. When starting on a new project, one of the first things to learn is how\nto run the existing tests, write new ones, and figure out the quickest way to\ndebug. Investing in faster tools, clearer error messages, and smoother processes\npays off - the shorter the feedback loop, the more confidently and efficiently\nyou can work.\n\n### Start Simple\n\nStart with something simple, then extend it further. Most often a complex\nproblem is a composition of simpler problems. If you are facing a problem and\nyou are afraid of the complexity it exerts, try to make a smallest possible step\ntowards the solution and see what you can do from there. Simple can also mean\nquick and dirty but that's ok as that's only a start. Once you have something\nsimple working you have a ground to move on further. Most likely this means you\nhave an **archetype** of a future thing, real and complex system.\n\nSee also Kent Beck's\n[Test-Driven Development book](https://en.wikipedia.org/wiki/Test-Driven_Development_by_Example)\nwhere this approach of doing simple things is explained at great depth.\n\n### Break Down Work\n\nDivide complex tasks into smaller subtasks. Keep breaking them down until each\nsubtask is short and manageable. This helps with detailed planning and assigning\nwork to the right people.\n\nAlign task breakdown with the technical architecture and vice versa. Ideally,\nwork packages, epics, or tasks correspond to functional components. This way,\nworking on a task means focusing on a single component or closely related parts.\nDesign the architecture so that work naturally fits its components.\n\nFor splitting software components, see Separation / Partitioning. Also, check\nPoint of View for more context.\n\n### Look Outside Your Immediate Task, Maintain the Bigger Picture\n\nWhen starting any task, take time to understand the rationale behind it (the\nWHY). See how the task connects to broader goals, milestones, or parallel\nefforts. It may be part of a chain where upstream or downstream effects matter.\n\nMaintain awareness of the bigger picture. A task that seems minor may be a\ncritical blocker for a more visible effort. Conversely, something that appears\nsimple might turn out to be time-consuming and affect teammates or dependencies.\n\nWith a deep understanding of the task, you will start seeing how different\nstrategies (e.g., Strategy X vs. Strategy Y) can lead to different outcomes.\nThis kind of insight allows you to:\n\n- Escalate risks early.\n- Spot opportunities.\n- Align better with the system's or team's needs.\n\nA practical application of this mindset in documentation writing: start each\ntechnical page with a clear problem statement and a description of its\nsurrounding context.\n\n- Who or what benefits if this task is completed?\n- Does it enable a system, a process, or a team?\n- What is the strategic value of solving it?\n\nFraming the problem this way helps readers, especially future engineers, orient\nthemselves and understand the significance of the solution that follows.\n\n### Avoid Work That Can Be Avoided\n\nBefore starting or planning any work, always ask: Is this work truly necessary?\n\nSometimes, tasks are initiated based on uninformed decisions, leading to work\nthat ultimately provides little value or fails to achieve the desired outcomes.\n\n\"Busy work\" refers to inefficient tasks that consume time and resources while\ncontributing little to the project's success. It can compromise schedules,\nreduce technical consistency, lower team morale, and create the illusion of\nprogress. The ability to recognize and eliminate busy work is one of the skills\nthat distinguishes a senior engineer from a junior one.\n\nEngineering is sometimes cheating. Instead of implementing something\nsophisticated, a smarter workaround can achieve the same result with far less\neffort. For example, rather than building a solution from scratch, reuse\nexisting work - whether by leveraging open-source software or buying an\noff-the-shelf system.\n\nIn software development, there's a well-known saying: \"The best code is the code\nthat is never written\".\n\n### Understand and Respect the Customer\n\nTake time to deeply understand and respect the customer, both the people and the\ndomain they operate in. Immerse yourself in their context. Know what they care\nabout, what problems they face, and how your work fits into their world.\n\nWhen things go smoothly, this understanding helps you deliver real value. When\nthings get challenging, such as when delays or technical setbacks arise, this\nrelationship matters even more.\n\nIn such situations, transparency is better than defensiveness. A clear and\nhonest update, even when delivering bad news, builds trust. Customers almost\nalways prefer being informed early over being surprised later. A transparent\nexplanation of issues, trade-offs, and risks shows respect for their time,\nplanning, and decision-making.\n\n### Choose Where to Innovate (Carefully)\n\nInnovate where your business's focus lies and stay conservative with other areas\nby using established technologies.\n\nFor example, a company focused on rocket software should likely avoid building\nits own web framework or NoSQL database. Exceptions exist, but they are rare,\nespecially when a company diversifies into a highly successful product unrelated\nto its core business. Innovating in too many areas can compromise the core\nproduct and cause missed deadlines.\n\nFor a great explanation, refer to this\n[Boring Technology presentation](https://boringtechnology.club/).\n\n### Automate everything\n\nSeek opportunities to automate processes or tasks. Automation eliminates busy\nwork, freeing time for more valuable activities. It reduces human error,\nincreases efficiency, and helps to maintain consistency. The best workflows are\nautomated ones.\n\n### Quick exploration\n\nThe solution you're looking for might be just two clicks and a couple of Google\nsearches away.\n\nWhen reading large documents, it can be helpful to \"fly over\" them to quickly\nlocate the most relevant section rather than reading from A to Z.\n\nWhen exploring with code, a combination of quick-and-dirty scripts can sometimes\ncreate miracles, giving immediate and valuable insights. Instead of discarding\nan idea because it's complex and time-consuming, try implementing a very basic\nversion first because it might provide useful insights or even a functional\nsolution right away.\n\n### Stay Curious and Explore\n\nLook around and see what is new in your field. If you find a cool project, check\nwho made it and what else they have done. Go to conferences and events. Being\ncurious helps you learn new things, meet new people, and get fresh ideas.\n\n### Task Sequencing: Group Related Activities for Efficiency\n\nWhen sequencing tasks (especially repetitive ones), group related tasks together\nand separate them from others.\n\nOne useful pattern is the 'Inbox' approach, where input is first collected and\nthen executed upon. For example, when writing a technical document, split the\ntask of gathering the document content (the 'Inbox' with bullet points) from the\ntask of formulating and spelling out each individual content item.\n\n### Plant the Tasks, Let the Mind Work\n\nWhen you are overloaded with several non-trivial tasks, try starting all of them\nby just 10-20%, then switch between them. The goal is to build an initial\nunderstanding for each task and give your mind time to work on them in the\nbackground (what people often call \"sleeping on it\").\n\nThe subconscious is an interesting mechanism: when given a few active problems\nto process, it starts building structure around them even when you are not\nfocused on them directly. Later, continuing from that 10-20% starting point\noften feels much easier. The process can be repeated from 10-20% to 30-40% to\n50-60% and so on.\n\nThis approach also helps with communication: once you've started a task, you're\nalready in a position to ask questions, clarify assumptions, or unblock others\nwho depend on your output.\n\n### Strive for Clarity\n\nStrive for clarity in everything you do. Put in the effort to make the products\nof your work, or the aspects of the system you're working on, as clear as\npossible. Simplify complexity - either by reducing the complexity itself through\ndevelopment or, if that's not feasible, by explaining the details as clearly as\npossible.\n\nAvoid owning too many non-obvious details about your work that only you\nunderstand. Do not hold onto esoteric knowledge - de-esoterize it. Document it\nfor everyone to access.\n\nEncyclopedism or esotericism is an anti-pattern because it obscures common\nknowledge about the system for others.\n\n- Document everything, especially the most complex topics.\n- Use plain English and diagrams to explain complex topics to your colleagues.\n  Test your content with them to ensure it is accessible. If it's still unclear,\n  ask for their feedback to improve it.\n\n### Everything Explicit. No Magic.\n\nWhenever you face a choice between explicit and magic, always choose explicit.\n\n\"Magic\" is a term software engineers use for anything that is non-obvious,\nhidden, overly complex, or no longer suited to the system's current state.\n\nMaking things explicit requires a constant effort to ensure clarity, so that\nothers can understand your work without extra effort. A good test for\nexplicitness is whether understanding is immediate, with no mental effort or\nblockers when going through the material.\n\n### Disown Your Work by Sharing It with the Team\n\nDon't make yourself a bottleneck. If you know something complex, share it,\nexplain it to the team, document it, or build it into tools and processes. The\ngoal is to avoid being the only person who understands or owns a specific area.\nWhen knowledge is shared, the team becomes stronger and more resilient.\n\nThis doesn't mean giving up ownership. You can still lead and take full\nresponsibility for a topic. But the results, details, and usage instructions\nshould be part of the team's shared knowledge. Strong ownership and open\nknowledge go hand in hand.\n\n### Learn from Lessons\n\nDo something, then learn from experience. Don't forget - take deliberate time to\nreflect. The industry has developed several best practices for capturing lessons\nlearned:\n\n- Standards: Organizational and industry knowledge is captured in standards,\n  handbooks, guidelines, and best practices.\n- Post-mortems: When something goes wrong, those involved produce a structured\n  report about the event. Larger companies maintain databases of critical\n  incidents that employees can study to educate themselves.\n- Debriefs: After a meeting, the group discusses what went well or wrong.\n- Lessons learned documentation and meetings: After completing an important\n  activity, such as a project or milestone, the team takes time to reflect on\n  what went well or wrong, learn from it, and document the findings.\n\nLearning doesn't have to be only organizational - it can also be personal.\n\nExamples:\n\n- If a project was successful, what made it so? If a project failed, what were\n  the key contributing factors? How can it be improved next time?\n- Learning how to estimate software work better - what if a task was estimated\n  to take X weeks but actually took 3X? Wouldn't it be valuable to improve\n  estimation skills?\n- If one colleague is significantly more effective than another, what makes them\n  so? What tools, techniques, or habits contribute to their efficiency? Can\n  something be learned from them?\n- Observing bugs missed during code reviews - what types of bugs tend to escape\n  static analysis or peer review? What patterns can be identified to prevent\n  them in the future?\n\n### Use Diagrams\n\nUse diagrams as part of your daily work. A diagram can often explain far more\nthan several paragraphs of text.\n\nUse diagrams for:\n\n- Prototyping and documenting software\n- Pair programming\n- Hardware-software integration testing\n- Meetings (including external meetings)\n- Onboarding colleagues\n- Everything else where a good visualization helps\n\nThere are standards and conventions for creating diagrams, such as UML, but in\npractice, even very basic diagrams can be incredibly useful. Use simple shapes\nlike rectangles and arrows, avoid excessive colors or different shapes, and\nexpress your concepts with the fewest visual elements possible. Creating\ndiagrams that are too visually complex hinders understanding and reduces their\neffectiveness.\n\n## Teamwork and Communication\n\n### Agile Software Development Requires Strong Social Network\n\n**Agile Software Development Requires Strong Social Network**. This statement is\na generalization: This idea has been there from the beginning and since the\ninception of the [Agile Manifesto](https://agilemanifesto.org/), but the\nfollowing quote from Kent Beck helps to pinpoint it very clearly:\n\n\u003e In The Forest (more specifically on an XP-style team), we handle communication\n\u003e of design \u0026 implementation multiple ways:\n\u003e\n\u003e - Communicative code.\n\u003e - Readable \u0026 predictive tests.\n\u003e - A strong social network.\n\u003e\n\u003e It's only when there is a large audience for stable information (such as the\n\u003e JUnit API) that we resort to separate documentation.\n\nSee\n[Kent Beck - Anatomy of Oscillation](https://tidyfirst.substack.com/p/anatomy-of-oscillation).\n\n### Sending Status Updates to the Team\n\nSoftware engineering teams often communicate daily via chat. A proven pattern is\nfor each team member to send updates about their work, allowing the entire team\nto see these messages.\n\nExamples of such messages include:\n\n- \"Task X is done, here's the PR link. @A and @B, could you take a look?\"\n- \"This week my focus is... Next, I am going to work on...\"\n- \"I see your PR, but I'm working on something else.\"\n- \"What does the team think about introducing the coding convention ABC?\"\n\nWhile this may seem obvious for some teams, there are others where daily chats\nare completely silent, reflecting a lack of communication between peers\nthroughout the day. When messages are exchanged, it creates a certain \"pulse\"\nwithin the team, signaling that the group is actively working on meaningful\ntasks and is open to discussion, iteration, and improvement.\n\nThis activity not only serves an informational purpose (increasing awareness)\nbut also has learning, motivational, and even entertaining aspects.\n\n### Close the loops, acknowledge communication\n\nA \"loop\" refers to any situation where one action is followed by another that\nresolves the first action in some way. Often, these loops are explicitly called\n\"feedback loops\" because they are closed with feedback that resolves an\noutstanding action or state, such as marking it Done, OK, ACK, or something\nsimilar.\n\nLoops can exist in both developed systems and producing organizations.\n\nExamples of loops:\n\n- Answering an email from an existing email thread closes the loop created by\n  that thread.\n- Closing a Pull Request finalizes its status, either as Done or Won't do.\n- Closing a work item ticket to Done.\n\nA task manager is an excellent tool for tracking work items that need to be\ncompleted and closed. For tracking non-trivial project development topics and\ntrade-offs, a useful practice is to maintain an \"Open Questions Log\" - a table\nwhere each unresolved or unclosed item is tracked by its current status until it\nis resolved.\n\nSometimes a loop may never be closed, or it may be closed with a significant\ndelay. Both scenarios can lead to potential problems or even hazards, depending\non the type of system being developed.\n\nNote that 'Won't-do' is also a valid way to close the loop. For example, closing\na Jira ticket with \"Won't do\" or \"Won't fix\" positively acknowledges that this\nwork will no longer linger in someone's backlog.\n\nNot closing loops is often bad practice. Some examples include:\n\n- Not answering an email can cause project delays or result in the\n  implementation of a broken or inconsistent system, leading to incidents or\n  accidents in the future.\n- A missed or forgotten chat message may mean important information is never\n  delivered to a critical person.\n- A manager neglecting to follow up on an important topic raised by employees,\n  leaving it unresolved in an inbox without due attention.\n\n### Keep Everyone in the Loop\n\nShare regular updates with the people who rely on your work: your manager,\nteammates, or anyone following your technical progress. In fast-moving projects,\nkeeping others informed helps avoid surprises and keeps everyone aligned.\n\nIn an office setting, updates often happen naturally. If the team is\nwell-connected, these updates may happen through casual conversations or small\ntalk over lunch. This kind of informal communication spreads useful information\nwithout needing formal meetings.\n\nOne big advantage: by the time your work reaches a review-like a code review,\ndocumentation review, or a project milestone-people will already know about it\nand may have given input earlier. This makes reviews faster, smoother, and less\nstressful.\n\nAnother reason to talk about your work: visibility and recognition. Others might\nnot know:\n\n- what challenges you re facing\n- how long something might take\n- how your work connects to theirs.\n\nYour teammates are often busy with their own tasks. Clear communication helps\nthem understand what you are doing and helps your work get noticed and\nappreciated.\n\nStay connected. Stay aligned.\n\n### Leverage Early Expert Review and Broad Feedback\n\nArrange work so that it passes through the eyes of multiple experts, for\nexample, through code reviews, design reviews, or walkthroughs. Share proposals\nwidely to gather diverse perspectives. This approach increases the likelihood of\nspotting issues early and improving the overall quality of the work.\n\nEspecially during the conception phase, share ideas early before anything is set\nin stone. Late feedback can either require costly rework or discourage reviewers\nfrom commenting at all, reducing the benefit of expert input.\n\nSee also [Cunningham's Law](https://meta.wikimedia.org/wiki/Cunningham%27s_Law):\n\"The best way to get the right answer on the internet is not to ask a question;\nit's to post the wrong answer.\"\n\n### Talk in Person When You Can\n\nIf something takes a lot to explain, talk in person instead of writing a long\nmessage. It is especially odd when people in the same office email each other\nabout things they could easily discuss face to face. Quick conversations often\nsave time and help avoid misunderstandings.\n\nSometimes, in-person chats mean interrupting someone, and that can be tricky,\nespecially if they are doing deep work. But often, if the topic involves both\npeople and can be resolved quickly, a short conversation is worth the\ninterruption and lets everyone get back to work with better clarity.\n\n### Recognize the ideas and achievements of your colleagues\n\nTeamwork involves contributions from all team members. Whether you are a leader\nor an individual contributor, it is essential to give credit where it's due when\nexpressing an idea that you know was authored by someone else.\n\nThis is a good practice because it fosters trust and respect within the team,\nencouraging open collaboration and the free exchange of ideas. Recognizing\nothers' contributions also boosts morale, motivates continued input, and\nstrengthens the overall effectiveness of the team.\n\nAn anti-pattern is when the names of the original authors are omitted, and the\nwork is presented in the first person, either intentionally or unintentionally,\nas if the content were one's own.\n\n### Praise Good Work\n\nDon't hold back from saying \"this is great\" when a teammate does something\nimpressive or puts in clear effort. This is especially important if you\nconsistently deliver high-quality work and expect the same from others. You\nmight give little feedback because excellence feels like the default. But even a\nsimple \"I see what you have done\" can make a real difference. It shows their\nwork is noticed and appreciated, and it helps build trust and connection within\nthe team.\n\n### Professional content\n\nWhen writing an email or chat message, even if addressed to a select group,\nconsider composing it in a way that it would remain professional and consistent\nif shared with a larger or unintended audience. Avoid using vague references\nlike \"we\" and \"they\", especially when referring to internal teams or external\nparties such as customers. Refrain from using negative sentences or excessive\nemotion. Your content should be polished and ready to be forwarded by anyone, at\nany time, whether intentionally or unintentionally.\n\n### Loop in Experts for Important Actions\n\nWhen making an important decision, involve the right experts. It is better to\ninclude too many people than to miss someone who should have been part of it.\n\nIf you are writing an email or message that speaks for your team or group, check\nit with others first. Make sure the message reflects what everyone agrees on.\n\nWhen a message is aligned like this, it:\n\n- Stays strong even if people question it.\n- Builds trust inside and outside the team.\n- Shows that the team is working together.\n\nTaking the time to check with others makes your message clearer and more\npowerful in the long run.\n\n### Share What Works\n\nWhen something works well for you, don't keep it to yourself and share it with\nyour team. Everyone has their own way of doing things, but your helpful habit or\nmethod might also work for others. A small improvement shared across a team can\nhave a big impact.\n\nIt is not always obvious what counts as a best practice. But if your approach\nmakes things easier or more effective, and it is different from how others work,\nthat could be a sign it is worth sharing or at least suggesting.\n\n### Share and Improve Team Workflows\n\nTry to make daily workflows something the whole team can share. Instead of\neveryone using their own custom scripts, set up a common, flexible system for\ntasking, development, testing, and debugging. Tools that are used and improved\nby many people evolve faster, and the whole team becomes more productive.\n\n### Keep Team Knowledge Alive\n\nTeam knowledge and culture can fade over time, just like a person's memory.\nImportant ways of working and shared values will not last unless they are\nrepeated and practiced regularly. If not, they slowly disappear from the group's\nhabits.\n\nTo keep them alive, teams need to refresh what matters. Say the important things\noften. Practice them together. Repeating is not just a reminder, it is how a\nteam holds on to what makes it strong.\n\n## Complexity and Cognitive Load\n\n\u003e \"Complexity can be defined as intellectual unmanageability\" (Nancy Leveson,\n\u003e Engineering a Safer World, p.4)\n\nhttps://en.wikipedia.org/wiki/Cognitive_load (and Cognitive Overload)\n\n### Solving Right Problems\n\n\"Engineers are great at solving problems but they are not always great at\nidentifying the right problems to be solved\" (Dr. John Thomas, ESWC 2019).\n\n### Solutions are Context-Driven\n\nEven the best solution to a problem is valid only within a given context. A\nslight change in the context can invalidate the solution, requiring one to start\nfrom scratch. This understanding highlights that no solution is universally\nperfect. Instead, solutions address specific problems or contexts in an \"optimal\nenough\" way. It also encourages detachment from ego-driven perfection, allowing\nsolutions to evolve as the environment changes.\n\nExamples:\n\n- A clean architecture or pattern may shift to a completely different, sometimes\n  opposite, solution due to changing requirements or system environments.\n- A \"perfect\" solution might be discarded because a new team or team leader\n  dislikes technology X and prefers technology Y, or simply because it aligns\n  with emerging industry trends.\n- Perfectly clean code may be rewritten and become more obfuscated due to\n  necessary performance optimizations.\n- Highly efficient code might be rewritten to sacrifice performance in favor of\n  better maintainability and readability, especially for a larger team.\n\n### Weakest link\n\nA piece of information is only as clear as its most ambiguous piece. This is a\ngeneralisation from the following fragment from \"Patterns for Writing Effective\nUse Cases\" by Steve Adolph et al., Chapter 6.6:\n\n\u003e Like the old proverb, \"A chain is only as strong as its weakest link\", a use\n\u003e case is only as clear as its most ambiguous step.\n\n### Point of View\n\n[How NASA Builds Teams](https://www.wiley.com/en-us/How+NASA+Builds+Teams%3A+Mission+Critical+Soft+Skills+for+Scientists%2C+Engineers%2C+and+Project+Teams-p-9780470456484):\n\n\u003e The right coordinate system can turn an impossible problem into two really\n\u003e hard ones.\n\n[The Early History Of Smalltalk](https://worrydream.com/EarlyHistoryOfSmalltalk/)\n\n\u003e Watching a famous guy much smarter than I struggle for more than 30 minutes to\n\u003e not quite solve the problem his way (there was a bug) made quite an\n\u003e impression. It brought home to me once again that \"point of view is worth 80\n\u003e IQ points.\" I wasn't smarter but I had a much better internal thinking tool to\n\u003e amplify my abilities. This incident and others like it made paramount that any\n\u003e tool for children should have great thinking patterns and deep beauty\n\u003e \"built-in.\"\n\n### Periphery\n\nIf your reasoning is hindered by cognitive overload while trying to solve a\nproblem, and there's no clear first step toward a solution, take a step back and\nstart working with the Periphery. By cleaning up the periphery, you'll often\nfind that the core problem becomes clearer and more approachable.\n\nA good example is legacy code: issues in the periphery, such as poor variable\nnames, bad code formatting, or a disorganized folder structure, may seem\nirrelevant to the core issue. However, they still contribute to the cognitive\noverload.\n\nUnclear or messy periphery can be a constant attention sink. It leads to\nmistakes, confusion, and delays, especially when teams don't take the time to\nfix it. Recognizing periphery issues and acting on them is a skill in itself. It\nhelps to keep your mental state clear and reduces unnecessary mental work.\n\nSome examples of periphery issues:\n\n- **Naming**. Folders, files, documents, tools, and conventions. Poor or\n  inconsistent naming alone can create significant noise.\n\n- **Code formatting**. Inconsistent or sloppy formatting adds friction for\n  everyone.\n\n- **Structure**. Disorganized codebases or documentation make it hard to find\n  and understand things. See also: \"Encyclopedic Document\".\n\n- **Responsibilities**. Incorrect or unclear class and component\n  responsibilities, even far from your immediate task, add to cognitive load.\n\nAnother word for Periphery is Background, see also\n[Deconcentation of Attention](http://deconcentration-of-attention.com/).\n\n### Rational and Unconscious\n\nEngineers create rational artifacts that may appear simple and mundane. However,\nthe process behind their creation often involves deep reflection and can stem\nfrom the unconscious mind.\n\n### Engineering as Input/Output: The Role of Prepared Inputs\n\nAn engineer can be thought of as an I/O device that transforms inputs into\noutputs. The output, a solution or an implementation path, is often clear when\nthe inputs are well-prepared and mature.\n\nWhen someone struggles to solve a problem, it is worth examining the quality of\nthe inputs. Some may be missing, incomplete, inconsistent, or incorrect. Once\nenough information is collected and clarified, the solution often emerges\nnaturally.\n\nInputs can take many forms: a precise task definition, a clear system\ndescription, a solid understanding of the environment, personal experience and\nskills, well-formed mental models, effective diagrams, synergies with existing\ncomponents, or advice from a fellow engineer. The healthier and more complete\nthe inputs, the more straightforward the path to a solution becomes.\n\n### Humans are not designed for Big Numbers\n\nIf you have to work with something that involves a big number of entities, like\ndo something on 10000 files or work with megabytes of data, start with reducing\nthis quantity to a minimum possible number of entities so that still makes sense\nfor a prototype of your final work: make it work with 1 file instead of 10000 or\nwith 20 bytes instead of 20 gigabytes.\n\n### There is no such thing as Many\n\nMany does exist but it is difficult to cognize with a human mind. Many needs an\nUmbrella, that turns it into One in the way we think about it. Many can be\nhomogenous like Array of objects of the same type or heterogeneous, for example\na bunch of instructions in the code or multiple functions in a test class or a\nset of User Profile fields of various types: name (string), age (int), settings\n(object). Collections are easier because they hide Many from us behind a\nwell-defined interface: `containsObject`, `getAtIndex`, `enumerateWithIndex`,\nwhich saves us from dealing with Many directly. Heterogeneous Many is harder:\nyou have to cognize and organize it yourself: group instructions into meaningful\nfunctions, group fields into meaningful containers like structs or database\ntables.\n\nOne programming construct that fails to constrain Many is tuple: you start doing\nthings like `let person = (\"John\", 32)` and `let (name, age) = person` or things\nlike `person.1` but then you quickly find yourself in a mess when the number\ngrows to a real Many (quick lesson: don't use tuples, use structs!). If you have\nMany, find a way to think and work with it like One.\n\n### 0-1-2-Many I\n\nMost of the people start saying \"so many\", \"infinite\" when there is actually 3\nor 4, rarely more, things on the table. Variation is 1a, 1b, 2a, 2b which is\nstill within limit of 3 or 4. This looks like ancient calculator: when 0, 1, 2\nand then 'many'. Algebra looks fairly simple: 0 + 1 = 1, 1 + 1 = 2, 2 + 1 =\nmany, 2 + 2 = many, etc. Consequence: people are quite susceptible to small\nnumbers. Say something like \"this consists of 3 steps\" and people will get it.\nDon't say \"seven\". See also **Humans are not designed for Big Numbers**.\n\n### 0-1-2-Many II\n\nDon't start to abstract or DRY from just two things. Wait until you have at\nleast 3 of them. See also **Duplication is better than poor abstraction**.\n\n### Masking (Shadowing)\n\nMasking/shadowing of all kinds is dangerous and should be avoided or treated\nwith a great care.\n\nExamples:\n\n- errors introduced to the systems when overlapping requirements are implemented\n  over time\n- masking in MC/DC\n- shadowing of variable declarations\n- typographically ambiguous symbols with overlapping visibility like `l` and\n  `1`, `O` and `0` (see MISRA guidelines)\n- code reviews: real bugs can hide behind less important but more noticeable\n  issues like typos or coding style details\n- bugs often hide themselves behind complexity\n\nSee also Overlapping Control.\n\n## Design\n\n### Functional analysis and decomposition\n\nBefore implementing a system in software or hardware, it is essential to\nunderstand what needs to be done, assign tasks and interfaces between teams,\nplan the work, and allocate resources. A common approach to achieve this\nunderstanding is Functional Analysis, which enables Functional Decomposition,\nalso called Functional Partitioning.\n\nFunctional analysis focuses on what the system must do, rather than how it is\nimplemented. At this stage, logical-not physical-aspects of the system are\nanalyzed. The primary goal is to identify system functions and their\nrelationships.\n\nAn early outcome of this analysis is a preliminary functional block diagram,\nwhich splits the system into logical parts. Once the system's contours are\ndefined, the next step is to determine meaningful interfaces between these\nblocks. Well-defined interfaces ensure modularity and clear interactions when\nthe system is implemented physically.\n\nThe challenge is to partition functions efficiently, avoiding over-engineering\nwhile maintaining flexibility and clarity. If the initial splitting of functions\nis inefficient, the functions are regrouped, producing a new version of the\nfunctional decomposition. This process is repeated iteratively until the\nfollowing conditions are met:\n\n- All functions are captured, leaving no gaps in functional coverage.\n- All functions are partitioned in the most efficient way, given the team's\n  knowledge and experience.\n- All functions are ready to be transformed into a work breakdown structure\n  (WBS) that makes sense from both programmatic and technical perspectives.\n\nWhen functions are well-separated, mapping them into future work packages\nbecomes straightforward. For example, Function 1 can become Work Package 1,\nFunction 2 becomes Work Package 2, and so on. Aligning tasks with functions\nallows teams to focus on their work independently while interacting through\nclearly defined interfaces. This alignment improves efficiency and minimizes\nblocking between teams.\n\nEffective functional partitioning leads to a robust WBS and clear separation of\nresponsibilities. Conversely, poor or incomplete functional analysis can result\nin inefficiencies, unclear responsibilities, and slow progress.\n\nBesides supporting the derivation of the WBS, functional decomposition also\nenables other activities that contribute to robust technical work partitioning.\nFor example, safety, reliability, performance and other analyses can be carried\nout early in the project, even before actual technical development begins. A\nwell-structured functional decomposition can support these analyses efficiently\nand provide feedback that may suggest more optimal functional splits, resulting\nin a safer, higher-performing, and more efficient system. Early integration of\nsafety analysis into functional design helps prevent risks and potential\naccidents, avoiding issues that could arise if safety considerations were\ndelayed.\n\nFunctional decomposition is recursive. After identifying top-level functional\nblocks (e.g., F1, F2), each block can be further decomposed into sub-functions\n(e.g., F1.1, F1.2). Complex projects may produce a deep functional tree, with\nhigher levels managed by the core team and lower levels delegated to suppliers\nor subcontractors.\n\nAn excellent quote from Systems Engineering Fundamentals captures the essence of\nfunctional decomposition and partitioning:\n\n\u003e **Functional Partitioning** Functional partitioning is the process of grouping\n\u003e functions that logically fit with the components likely to be used, and to\n\u003e minimize functional interfaces. Partitioning is performed as part of\n\u003e functional decomposition. It identifies logical groupings of functions that\n\u003e facilitate the use of modular components and open-system designs. Functional\n\u003e partitioning is also useful in understanding how existing equipment or\n\u003e components (including commercial) will function with or within the system\n\nSee\n[SYSTEMS ENGINEERING FUNDAMENTALS](https://ocw.mit.edu/courses/16-885j-aircraft-systems-engineering-fall-2005/6128a102c1a9b6dbd30f2fb18c12aa64_sefguide_01_01.pdf).\n\n### Poor Abstraction\n\n\u003e Duplication is better than poor abstraction (Sandi Metz, Rails Club 2014,\n\u003e Moscow).\n\n\u003e \"...ill-fitting structure is worse than none...\" (Eric Evans - Domain-Driven\n\u003e Design, p.446)\n\nA good example from https://www.sigbus.info/worse-is-better:\n\n\u003e In lld v2, we decided not to use an intermediate representation. Instead, we\n\u003e directly handle platform-dependent native file formats. lld v2 consists of\n\u003e virtually three different linkers for Windows, macOS and Unix. They share the\n\u003e same design but do not share code. Naturally, we sometimes had to write very\n\u003e similar code for each target. This may seem like an amateur-level programming\n\u003e mistake, but in reality, it's much easier to write straightforward code for\n\u003e each target than writing unified one that covers all the details and corner\n\u003e cases of all supported targets simultaneously.\n\n### Cost of Abstraction\n\nSoftware engineering often involves creating abstractions. A solution to a\nproblem can include more or fewer abstractions, but each introduced abstraction\ncomes with a cost. This cost manifests as the cognitive burden placed on those\nwho need to understand, maintain, and document it - not just in code, but also\nin models, documentation, and even organizational structures.\n\nCognitively, an abstraction can be thought of as a mental gadget that one must\n\"install\" in order to work with it. Imagine an empty room that needs to be\nfurnished according to a specific use case. If the chosen abstractions fit well\nwithin the team's mental model, the space remains functional - like a\nwell-furnished room where people can move freely and use it as intended.\nHowever, if abstractions are difficult to grasp or combine in contradictory\nways, the mental space becomes cluttered, leaving little room to maneuver. This\nis similar to a room overloaded with furniture, making it difficult to navigate\nor even understand its intended purpose.\n\nFor example, if a team introduces a new abstraction X, it incurs the following\ncosts:\n\n- Every developer must understand and adopt X to work effectively within the\n  system.\n- The system must be structured around X in a way that ensures maintainability\n  over time.\n- Long-term maintenance will require keeping the code, file structure, and\n  models aligned with X, often introducing additional overhead.\n\nIntroducing too many incompatible abstractions - or a few abstractions that\nconsume too much of the decision space - can quickly lead to over-engineering.\nThose responsible for maintaining such systems often find themselves\ndisentangling unnecessary complexity, seeking a new balance that restores\nmanageability by replacing or introducing more adequate abstractions.\n\n### Habitability\n\nHabitable software is better than perfect software.\n\n[Richard Gabriel - Patterns of Software, Habitability and Piecemeal Growth](https://www.dreamsongs.com/Files/PatternsOfSoftware.pdf).\n\n\u003e Habitability is the characteristic of source code that enables programmers,\n\u003e coders, bug-fixers, and people coming to the code later in its life to\n\u003e understand its construction and intentions and to change it comfortably and\n\u003e confidently. Either there is more to habitability than clarity or the two\n\u003e characteristics are different...\n\n\u003e ...Habitability makes a place livable, like home. And this is what we want in\n\u003e software - that developers feel at home, can place their hands on any item\n\u003e without having to think deeply about where it is. It's something like clarity,\n\u003e but clarity is too hard to come by.\n\n### Hard Feature\n\nIf a feature is hard to implement it might indicate that it is something wrong\nwith the feature (or product).\n\n### True Name\n\nIf you know [True Name](https://en.wikipedia.org/wiki/True_name) of something\nyou have power over it. Good class name - this is what True Name is in OOP.\n\n\u003e \"A well-chosen word can save an enormous amount of thought\", (said by Mach\n\u003e according to S.R.Cajal, Santiago Ramón y Cajal, \"Advice for a young\n\u003e investigator\")\n\nSee also\n[Mass and Gravity](http://www.carlopescio.com/2008/12/notes-on-software-design-chapter-2-mass.html).\n\n### One Pattern per Class\n\nA class violates Single Responsibility Principle if it contains implementation\nof more than one design pattern. Of course there are exceptions.\n\n### Archetype\n\nArchetype is an umbrella concept for other concepts like: `prototype`,\n`proof of concept`, `minimal viable product`. Archetype means something simple\nand coherent. If you know the archetype of something you understand the essence\nof it. A complex system can be traced back to a one or a number of underlying\narchetypes.\n\nInteresting side note: as far as I see it, the tendency is that engineers as\nthey grow their software bigger, do not care much about the underlying\narchetypes. Imagine how easy it would be to learn about the software if it would\ncontain itself in its earliest forms of being (source code, documentation,\ndrafts etc). Great example: Rust programming language had to start from\n[somewhere](https://github.com/graydon/rust-prehistory).\n\n\u003e \"View the problem in its simplest forms ... An excellent method for\n\u003e determining the meaning of something is to find out how it comes to be what it\n\u003e is.\" (Santiago Ramón y Cajal, \"Advice for a young investigator\")\n\n### Prima Materia\n\nSometimes to make further progress you need to un-implement (break!) particular\npattern/architecture/solution and put it back into\n[Prima Materia](https://en.wikipedia.org/wiki/Prima_materia) state and only then\nthansform it into a something new. Metaphors similar to Prima Materia are\n\"primordial soup\" and \"indifferentiated soup of ideas\" (Eric Evans - DDD).\n\n### Mature automation\n\nMature automation allows itself to be observed, inspected, and overridden. Even\nif something is automated and usually works well, there should always be a way\nto turn it off or adjust it when needed. Good automation is transparent - you\ncan see what it is doing, understand how it works, troubleshoot problems, and\nmake changes if necessary. In some situations, it is important to bypass\nautomation entirely and take manual control or use an alternative path. Systems\nthat do not allow this create unnecessary friction and risk. Automation should\nsupport people, not trap them.\n\n### \"Magic\" is automation that is not adequate\n\nIn the beginning, there is no magic, but simply a desire to automate things to\nreduce repetition. Magic appears as a result of increasing complexity that makes\ncurrent solution to be inadequate for further progress. Magic can also emerge\nrather quickly as a result of automating wrong things from the beginning. The\nholy grail is automation that is always adequate.\n\n### Poisonous Systems\n\nBadly designed systems tend to poison systems they interact with.\n\n### Bad Design in House\n\nDo not overdesign your own software if you have a big producer of bad or too\nopinionated designs nearby. A big producer can be a vendor or a team with\nauthority who decided to rely on a given design a while ago.\n\n### Trade-off of Encapsulation\n\nStrong, \"tight\", encapsulation is good but don't forget about the users:\nOperations people. Good example is debugging facilities - if you close\neverything then you leave the ops people, who might be you, without any tools to\nunderstand or tweak your system. Richard Cook explains this very well: See\n[Velocity 2012: Richard Cook, \"How Complex Systems Fail\"](https://www.youtube.com/watch?v=2S0k12uZR14).\n\n### Unnecessary Flexibility\n\n(from [Writing Solid Code](http://writingsolidcode.com/))\n\n\u003e Flexibility breeds bugs. Another strategy you can use to prevent bugs is to\n\u003e strip unnecessary flexibility from your designs... The trouble with flexible\n\u003e designs is that the more flexible they are, the harder it is to detect bugs.\n\n\u003e ...Flexible features are troublesome because they can lead to unexpected\n\u003e \"legal\" situations that you didn't think to test for even realize were\n\u003e legal...\n\n\u003e ...When you implement features in your own projects, make them easy to use;\n\u003e don't make them unnecessary flexible. There is a difference. Don't allow\n\u003e unnecessary flexibility.\n\n### Black Box with a Green Play Button\n\nIdeal interface for a system of arbitrary complexity is a black box with a green\nplay button on it - you take the box, press green button and it just works. The\nsecond ideal interface is when you also have a red button to stop the system.\n\n### Single Source Concept and Its Exceptions\n\nThe Single Source (of Truth) concept is one of the first principles beginner\nprogrammers learn and often becomes a rule they follow rigorously. However, like\nmany principles in life, it has its exceptions. Blindly adhering to the Single\nSource rule can sometimes lead to suboptimal results.\n\nA good example of when this principle might fail is the\n[Poor Abstraction](#poor-abstraction) scenario. This happens when someone tries\nto consolidate similar elements into a single source while ignoring their\nsignificant differences. In such cases, forcing everything into one place can\ncreate an abstraction that is brittle, confusing, or overly complex, ultimately\nmaking the system harder to understand and maintain.\n\nAnother example is\n[Two Almost Identical Entities](#two-almost-identical-entities). This occurs\nwhen someone tries to merge two seemingly identical entities into one, which\nresults in an overly complicated \"Single Source of Truth\" codebase. This\napproach often leads to significant branching logic and reduced readability,\nmaking the code harder to work with and more prone to errors.\n\nUnderstanding when to apply the Single Source principle and when to allow for\nexceptions is crucial for achieving balance and maintaining flexibility in\nsoftware design. Learning where to follow and where to de-prioritize the Single\nSource principle is a good skill that distinguishes a more experienced\nprogrammer from a beginner one.\n\n### Resilience to Change vs Fixed Perfect Solutions\n\nWhen designing a system, there is a trade-off between making it easier to change\nin the future and striving for perfection. In most cases, choosing flexibility\nis the better option. If you anticipate changes in context or additional\ndevelopment work that could affect the system, avoid focusing too much on\nperfecting the existing solution, as it may not hold up under new pressures.\nAnother important consideration is the ability to undo or disable a function\nthat works perfectly now but could cause unforeseen issues in operation. Often,\na perfectly working solution can create obstacles for other systems or people\ninvolved in operating the system.\n\n### Two Almost Identical Entities\n\nOver the years I have seen at least three big units of a hardly manageable\nlegacy code where each of them was built on two almost identical entities. There\nare two ways of such things to co-exist:\n\n1. One is a subclass of the other.\n2. Two almost identical hierarchies are maintained.\n3. Two groups of helper functions without a clear separatation of\n   responsibilities between them.\n\nIt seems that historically in all three cases it started with one entity that\naccumulated its features along the way, then came the other which was so similar\nto the first that programmer avoided extraction of similar modules that both\nentities had and went with subclassing to get the result quickly or with 2\nparallel hierarchies.\n\nTo these days I still didn't see or create an elegant solution to this problem.\nSee also \"Hard Feature\".\n\n### Control\n\nOne of the key concerns is Control: where control should or should not be, what\nshould have control (be active) and what should not have (passive).\n\n#### Observable Control\n\nSoftware should be designed in such a way that there always should be a\ndedicated place where it is obvious how the control and work flow through the\nsoftware. This should be effective on all levels of abstraction and for each\nlevel of abstraction, such dedicated software should be free of the lower-level\nimplementation details that discourage easy understanding of context.\n\nIf something creates a low-level implementation noise on a given level, it might\nbe a good sign that one or more underlying lower layers exist where that\nlower-level implementation can be represented as a high-level workflow logic\n(sequence of steps or algorithm).\n\n#### Humans should dominate machines\n\nThe lower-level modules should not have control over higher-level modules. It is\nnot only about not having higher-level module imported in lower-level modules\nand making everything to work through protocols/interfaces but more about what\nis the flow of control: \"what controls what\". Two shortcuts: **humans should\ndominate machines**, **business logic should dominate the system's\nimplementation details**.\n\n#### Overlapping control\n\nOverlapping things is a challenge for a human mind and therefore is bad for the\nwhole software lifecycle: design, development, testing and maintenance. This\nmight be two or more classes that do the same thing. This might be two or more\npeople whose responsibilities overlap. Nancy Leveson says Overlapping Control is\none of the greatest sources of safety problems: two controllers whose areas of\nresponsibilities overlap (see \"Engineering a Safer World\"). See also \"Two almost\nidentical entities\" and \"Shadowing/Masking\".\n\n#### Broken control loops\n\nThe top-level controllers should always have a control over the bottom-level\nelements. If the controllers include both humans and automation, the humans\nshould always be able to intervene and take over the control provided by the\nautomation.\n\nThis heuristic can be turned into explicit design constraint.\n\n### Feedback\n\n#### Broken feedback loops\n\nMissing, insufficient or incorrect feedback is a great source of troubles for\nany system.\n\n\"All feedback loops must be closed\" - this heuristic can be turned into explicit\ndesign constraint.\n\n### Separation / partitioning\n\n- Separate stable from unstable\n- Separate permanent from temporary\n- Separate synchronous from asynchronous\n- Separate similar from different\n- Separate symmetrical from asymmetrical\n- Balance and symmetry: if one partition has way more items than the other ones,\n  this may indicate that the partitioning has not been complete.\n- Separate construction from operation (one example: Factory vs Command)\n- Separate content from presentation (applies to UI-heavy code, great example:\n  HTML/CSS)\n- Separate easy from complex: isolate easy, isolate complex, repeat many times\n- Separate stateless from stateful\n- Separate data from behavior and behavior from data unless you do have a good\n  OOP class/object with good data/behavior balance.\n- Separate general-purpose from application-specific\n- Separate application-level code from system-level code\n- Separate methods that read from methods that write\n- Separate decision from condition\n- Separate One from Many, separate Many from Many.\n\nExample 1: \"Monolithic test case files\"\n\nIn the following example the `_feature1_` or `_feature2_` parts and numbers in\nthe test method names assist a lot in logical grouping of the tested\nfunctionality.\n\n```c\n# Many group #1\ntest_feature1_1() {}\ntest_feature1_2() {}\ntest_feature1_3() {}\n# Many group #2\ntest_feature2_1() {}\ntest_feature2_2() {}\ntest_feature2_3() {}\n```\n\nExample 2: the inner block has a multiline routine which could actually be\nanother function that works on one. At the same time this inner block on many.\nUnless we create that another function we have a conflict between many of the\nenumeration and many of the instructions inside a block.\n\n```cpp\nEnumerateInstructions(*function, [\u0026](Instruction \u0026instr, int bbIndex, int iIndex)\n{\n  ... lots of lines working on `instr` ...\n});\n```\n\n### Grouping\n\n- Group together things that change at the same time. If possible create\n  container data structures so that a change involves a change of **one**. If\n  possible, group all the changes that happen at the same time together.\n\n- Group things that are used together.\n\n### Observability vs Correctness\n\nIncorrect but observable code can be more valuable long-term than correct but\nunobservable code. Observable code is easier to inspect, test, and improve, even\nif it contains mistakes. In contrast, correct but hidden code can become\ndifficult to maintain and debug over time, creating technical debt. Visibility\nallows for quicker fixes and ongoing improvement, making it more sustainable in\nthe long run.\n\n### Don't Use RAII on a Business Logic Level\n\nRAII is good for resource management, such as handling memory, file handles, or\nnetwork connections, where resources need predictable acquisition and release.\nHowever, applying RAII to business logic can lead to significant problems:\n\n- Reduced flexibility: RAII assumes that actions are tied directly to scope, but\n  business workflows may need to defer, combine, or otherwise manage actions\n  independently of object lifetimes.\n\n- Lack of transaction control: Business operations often involve external\n  systems, validation, or rollback mechanisms that require precise control. RAII\n  hides these processes behind object lifecycle management, making it harder to\n  handle errors or maintain consistency.\n\n- Unintended side effects: Business logic often involves workflows with complex\n  rules and dependencies. Tying actions like adding or removing data to the\n  lifecycle of objects can cause unexpected behaviors if those objects are\n  destroyed prematurely or unintentionally.\n\n- Debugging challenges: When business actions are implicitly triggered by object\n  lifetimes, it becomes harder to trace when and why specific operations occur.\n  This lack of clarity can lead to subtle bugs that are difficult to identify\n  and fix.\n\nInstead of using RAII, manage business logic explicitly through well-defined\nmethods or services. This approach keeps the logic transparent, easier to\nunderstand, and more adaptable to changing requirements.\n\n### Rich Collection of Models and Diagrams\n\nSince each model or diagram represents only a fragment of reality, no single\nview is sufficient for engineering work. To address different tasks effectively,\nan engineer needs access to a diverse set of models and diagrams. Each\nrepresentation emphasizes some aspects while concealing others, so a rich\ncollection ensures that the essential dimensions of reality can be examined from\nmultiple perspectives.\n\nAn engineer with a diverse set of modeling tools can communicate design ideas\nand reasoning far more effectively than someone who relies on no visuals or only\na single type of diagram. Multiple representations improve clarity and ensure\nthat all relevant aspects of the system are adequately considered.\n\nThe Unified Modeling Language (UML) offers a good illustration. Its suite of\ndiagrams is designed to capture both structural and behavioral aspects of a\nsystem. Class and component diagrams clarify static structures and their\nrelationships, while sequence, activity, or state diagrams describe dynamic\nbehavior over time.\n\n### The Limits and Choices of Models and Diagrams\n\nModels and diagrams represent selected aspects of reality. By definition, each\none captures only part of what it describes. The choice of a particular model or\ndiagram shapes which information is highlighted and which is hidden or\ndownplayed.\n\nThis has practical consequences. If the task at hand does not align with the\npurpose of the chosen model, the model may become distracting rather than\nuseful. A representation that clarifies one dimension of a system may obscure\nother aspects that are crucial for solving the problem.\n\nFor example, a static diagram showing functional blocks and their interfaces can\nclarify structural relationships, but it gives little insight into how those\nfunctions interact over time. Conversely, a time-based activity diagram\nillustrates temporal interactions well, yet it can obscure the functional\ngrouping of elements.\n\nReliability and safety analysis provide another illustration. A static\nstructural diagram can show how many elements exist and how they are grouped\nwithin a system or repository. Such a view offers an inventory of what can, in\nprinciple, be analyzed. However, it says little about control dependencies, how\nfailures propagate, how redundancies work, or how safety properties can be\nassured.\n\nRecognizing these limitations is essential. An architect, safety analyst, or\nengineer who understands the inherent constraints of models is better equipped\nto request or create additional representations when needed. Without the right\nmodels-or in the presence of misleading ones-analysis, design, and\ndecision-making can suffer.\n\n### Pseudocode as a Modeling Tool\n\nSometimes a fragment of pseudocode can capture the essential aspects of a\nprocess more effectively than an activity diagram. Where diagrams may become\ncluttered with blocks and arrows, pseudocode can provide a direct and precise\ndescription of the logic, making it easier to understand and communicate.\n\n## Coding, code reviews, and maintenance programming\n\n### Code that Works\n\nWorking code with a good-enough architecture is better than buggy code with a\nperfect but overly complex architecture.\n\n### Code Is Not Your Partner\n\nSometimes, you don't have to be nice to code.\n\n- It might be written for a different platform.\n- It could be outdated or rely on ancient build tools.\n- Some parts may be unnecessary for your needs.\n- It may contain mistakes.\n\nIn such cases, it is perfectly fine to delete, modify, or hack the code - to\nmake it compile, test it, or simply understand how it works.\n\n### Two Strategies for Replacing a Feature\n\nWhen replacing Feature A with Feature B, there are two broad approaches.\n\n1\\. Remove A, Then Implement B\n\nThis strategy is best when:\n\n- Feature A is simple.\n- Feature B can be developed quickly.\n- Switching to B is straightforward.\n\nIn such cases, removing A first and then building B works well, as the\ntransition is fast and manageable.\n\n2\\. Develop B in Parallel, Switch from A to B, Remove A\n\nThis approach is necessary when the transition is complex or time-consuming.\nInstead of removing A immediately, B is developed alongside it while the\nexisting system remains operational. The switch to B happens only when it is\nfully developed and tested. A remains available as a fallback until B is proven\nreliable, after which A can be removed.\n\nThis method is particularly useful when:\n\n- Feature B requires significant development time.\n- Switching from A to B is complex and requires a dedicated transition\n  mechanism.\n\nFor already deployed systems where downtime is unacceptable, the second approach\nis often the only viable way to ensure a smooth migration.\n\n### Smallest Scope\n\n- Restrict the scope of data to the smallest possible. (The Power of 10: Rules\n  for Developing Safety-Critical Code by NASA)\n\n### Code Style as a Blocker\n\nSometimes code style can be a blocker. Poorly formatted code can make\nunderstanding of it extremely difficult. Do everything to reduce your cognitive\nload. Real-world example:\n\n```swift\nlet expectedRemainingLoops = Int(ceil( (expectedRemainingElements - Double(currentRemainingElementsForLoop)) / Double(PPENumberOfTasksInCurrentLoop) ))\n```\n\nreads much better if\n\n```swift\nlet expectedRemainingLoops =\n  Int(\n    ceil(\n      (expectedRemainingElements - Double(currentRemainingElementsForLoop)) /\n      Double(PPENumberOfTasksInCurrentLoop)\n    )\n  )\n```\n\n### Simplifying Complex Feature Branches\n\nWhen working on a non-trivial feature branch, consider breaking it down into its\ncore functionality while separating any trivial or unrelated changes that can be\nintegrated independently.\n\nA complex branch can often become more manageable, or even medium in scope, when\ndistilled into its essential parts and split into smaller, separate changes. In\nsome cases, breaking it down properly can eliminate the complexity entirely,\nleaving only straightforward, incremental updates.\n\n### The Moving and Changing Anti-pattern\n\nA great anti-pattern that complicates code reviews is creating a changeset that\ninvolves both moving and changing things at the same time. This obscures the\ndiffs in the version control system, making it harder to track changes. The\nsolution: isolate moving and changing into separate commits or separate PRs.\n\n### Avoid Plural Names For Classes\n\nClasses should represent a single entity or concept. Naming a class in the\nplural form (e.g., `Users`) can confuse its responsibility, making it seem like\nit manages multiple instances. Instead, use singular names (e.g., `User`) and\nhandle collections separately, such as in a `UserList` or `UserRepository`. This\nensures clear, focused class responsibilities.\n\n### Fast Programming and Slow Programming\n\nThis can be viewed as prototype vs. maintenance programming. Fast Programming is\ncrucial for rapid progress and is often encouraged by the business. However, it\nrarely allows time to learn from mistakes due to the tunnel vision and \"straight\nahead\" thinking that often accompany it. Slow Programming, on the other hand,\nhas the virtue of reflection and deeper analysis, but it tends to be too slow to\nlaunch a business from scratch. Business leaders typically start to appreciate\nSlow Programming only when they hit the wall of complexity, realizing the need\nfor proper design.\n\n### Stable Components\n\nStable Components is a resort of a Maintenance Programmer. One way for a\ndeveloper to survive in a large legacy project is to create stable components or\nextract them out of existing mess of code. Stable component most likely means a\ntestable component: it can be a parsing module or API layer or string\nmanipulation helpers. Having such islands of stability helps a lot to overcome\nthe difficulties of a maintenance programming. See also Periphery and Prima\nMateria Heuristics.\n\n### Boring Code\n\n(from [Writing Solid Code](http://writingsolidcode.com/))\n\n\u003e If your code feels tricky, that's your gut telling you that something isn't\n\u003e right. Listen to your gut. If you find yourself thinking of a piece of code as\n\u003e a near trick, you're really saying to yourself that an algorithm produces\n\u003e correct results even though it is not apparent that it should. The bugs won't\n\u003e be apparent to you either.\n\n\u003e Be truly clever; write boring code. You'll have fewer bugs, and the\n\u003e maintenance programmers will love you for it.\n\n### Boring Code 2\n\nComplex software is not to be developed and used by average programmers. This\nhappens anyway because of production pressures. People say: your mileage may\nvary.\n\n### Lack of Knowledge\n\nBad code stems from a lack of knowledge, not malice, even though both bad code\nand malice share unawareness as their root cause. Sometimes, it helps to put on\na \"lack-of-knowledge hat\" to better understand the intentions behind the code\nyou're reading.\n\n### Lack of Knowledge II\n\nAn interesting feature of inexperience is that it imposes limits on a software\nsystem's ability to scale. Software written with unawareness at its core will\neventually become rigid and nightmarish, to the point where team members start\navoiding the \"dark forest\" of its codebase. The natural consequence is that such\nsoftware reaches an upper bound of complexity. Paradoxically, this means that\nsomeone tasked with re-engineering it will often find its complexity manageable\nin the end.\n\n### Goodwill vs Pain\n\nMuch of what we programmers learn over the years comes from pain, not from\ngoodwill.\n\n## Biases\n\n### If It Works, Then It Works Bias\n\nOne of the common cognitive biases in engineering is the assumption that if\nsomething works, it must be good enough. This belief often surfaces during\nreviews of code, design, or systems that have passed tests or are known to\nfunction under specific conditions. It takes conscious effort to question\nsomething that already appears successful.\n\nBut just because something works under one set of constraints does not mean it\nwill hold up under others. Often, \"it works\" simply means \"it works here and\nnow\".\n\nTo counter this bias, reviewers should look beyond surface-level functionality\nand ask:\n\n- It works with a file of size X. What about 10X or 100X?\n- It works under normal conditions. What about a slow network or high CPU load?\n- It works on Linux. Will it behave the same on embedded hardware?\n\nThis bias also affects how we treat existing systems. A solution that \"has\nalways worked\" may be treated as correct by default, leading to investigations\nbased on flawed assumptions and missed problems that emerge under different\ncircumstances.\n\nThere's no silver bullet for overcoming this bias. The key is maintaining\ndeliberate skepticism and making a habit of viewing solutions from multiple\nangles.\n\n### Focusing only on what's most visible bias\n\nThe tendency to concentrate a review or investigation on the most obvious,\nobservable, or symptomatic parts of a system, rather than systematically\nconsidering all potential contributing factors. This can lead to overlooking the\ntrue root cause, especially if it's hidden in a less familiar or less accessible\narea. Before jumping on a specific part of the problem or solution, first step\nback and consider the bigger picture - which blocks in general might be\ninvolved. As per the common saying: \"Don't look only where there is light\". In\npractice, this means listing all possible contributors to a problem in the form\nof a block diagram or any other simple sketch that collects both the symptoms\nand relevant system parts. It can also help to annotate each block with relevant\nproperties - for example, in a performance investigation, adding performance\ncharacteristics per block can highlight which parts are likely causes, not just\nthe ones that appear most problematic.\n\n### The Fix Bias\n\nWhen reviewing a pull request titled \"Fixes XYZ\", there is a natural tendency to\ntrust the new change more than the existing code. This bias arises from the\nassumption that the previous implementation was flawed simply because it is\nbeing replaced. As a result, one might overlook the consequences of the fix or\nfail to rigorously verify the correctness of the new change.\n\nTo mitigate this bias, it's important to evaluate both the old and new\nimplementations with equal scrutiny. Consider questions such as:\n\n- Is the problem being solved accurately identified?\n- Does the new change address the issue without introducing new problems?\n- Are the trade-offs of this fix justified compared to the original\n  implementation?\n\nBy being aware of this bias, reviewers can ensure a more balanced and thorough\nreview process.\n\n### Resolving Merge Conflict Bias\n\nSoftware engineers frequently resolve merge conflicts, and while this task is\noften trivial, it presents opportunities for introducing subtle bugs. One\ncontributing factor is the cognitive bias that favors accepting newly introduced\nchanges over preserving existing behavior.\n\nThe conflict markers (`\u003c\u003c\u003c \u003e\u003e\u003e`) used by Git can obscure important details of\nthe original code, making it easy to unintentionally discard necessary logic.\n\nA practical approach to mitigating this risk is to slow down and carefully\nevaluate both conflicting versions. Consider not just the new change, but also\nwhat might be lost if an existing line or code chunk is removed. Reviewing the\ncode in context and testing after resolving conflicts can help prevent\nunintended regressions.\n\n## Reliability\n\n### Errors are not ok\n\nNever ignore errors. Presence of errors indicates that you don't understand your\nsystem well enough and therefore don't have a full control over it.\n\nAn error can be major or minor but it anyway contributes negatively to the\ndesign and operation of your system and also to your understanding of it (see\n[Periphery](#periphery)).\n\nErrors typically ignored by developers include:\n\n- Configuration errors\n- Compiler warnings\n- Build system errors\n- Errors produced by the test suites (flaky tests)\n\n### Errors must be understood and described\n\nGoogle for `Malfunction 54` for a good example.\n\n### Underlying errors shall not be hidden\n\nIf a higher-level error wraps some other underlying error, the information about\nthe underdying error shall not be lost. Instead, it should be fully available to\nthe higher-level error for error handling, logging, tracing, etc.\n\n### Critical errors vs non-critical errors\n\nMake a clear distinction between critical and non-critical errors on all levels:\nsource code, software design, error reporting, documentation.\n\n### Assertions are better than no error handling\n\nWhen there is no error handling, presence of asserts gives at least some basic\nguarantee that software does not do what it is not supposed to.\n\n### Assertions are shortcuts for a proper error handling\n\nEvery assert becomes a proper error handling eventually.\n\n### Crash Early\n\nIf you know how to not program defensively in a particular situation go ahead!\nOtherwise make your code to Crash Early to catch bugs as early as possible: use\nsensible assertions and stress edge-cases with tests. See\n[Some notes C in 2016: Code offensively](http://blog.erratasec.com/2016/01/some-notes-c-in-2016.html#.VtGEKBg7T5c)\nand\n[Spotify engineering culture (part 2): \"We aim to mistakes faster than anyone else\"](https://labs.spotify.com/2014/09/20/spotify-engineering-culture-part-2/).\n\n## Testing\n\n### Write Tests, Even Bad Ones\n\nIf you do not write tests, you will never learn how to write them. It's better\nto write bad tests than to write none at all.\n\n### TDD as a Toolbox\n\nThe ability to do Test-Driven Development (TDD) is not a binary \"can or cannot\"\nskill. It's about having a wide range of techniques, patterns, tricks, and hacks\nin your toolbox. When you have enough of them, you can test almost anything in a\nreasonable amount of time.\n\n### Legacy Code is Code Without Tests\n\nAs Michael Feathers puts it in Working Effectively with Legacy Code, \"Legacy\ncode is code without tests.\"\n\n### Testing as a Way to Manage Complexity\n\nIn addition to ensuring quality, testing is essential for simulations that help\nmanage complexity. If I can test and simulate every aspect of my program, I can\neffectively manage its complexity. However, if there are blind spots - areas\nthat are difficult or impossible to test - I lose control over those areas and\nmust rely on real users to test in the wild.\n\n### Test It to Engineer It\n\n\"If you can't measure it, then it can't be called engineering\" (Ivar Jacobson,\nObject-Oriented Software Engineering: A Use Case Driven Approach). We can\ninterpret \"measure\" as \"test\", with testing serving as both a form of\nmeasurement and a core part of engineering.\n\n### Improve Testability\n\nIdeally, everything should be testable. If something is difficult to test, it\noften signals a need to improve code quality, toolset, or testing\ninfrastructure. With effort, these can be enhanced. If unsure how to test\nsomething, start with a simple approach: stub everything, simplify the network,\nassert what's necessary, then iterate on refining both the test and the system\nunder test (SUT).\n\n## Distribution\n\n### Provide Basic Test Sequences with Your Product\n\nIf you are a provider of software or hardware, consider going beyond the\nstandard \"interface control document\" (ICD) by including basic test sequences -\na \"Hello World\"-type program that allows users to quickly get started with your\nproduct. Such examples help users bring the system online and get up to speed\nwithout unnecessary guesswork.\n\nThe lack of clear \"Hello World\" or how-to documentation is especially prevalent\nin the embedded software industry, where companies often rely solely on ICDs or\ntechnical reference manuals. This forces end-user software engineers to engage\nin guesswork and reverse-engineer the documentation to figure out how to bring\nup a device. While the industry is gradually improving in this regard, there is\nstill a long way to go. By providing a clear and functional \"Hello World\"\nexample with every product, you empower your users and make adoption of your\nproduct much smoother.\n\n### Provide Drivers Alongside Your Hardware\n\nIf you are a hardware provider, consider supplying software drivers with your\ndevice rather than just a technical reference manual for end-users to decipher\nand implement. As the developer of the device, you understand its functionality\nbetter than anyone else. By providing ready-to-use drivers, you save your users\nthe time and effort of implementing the device's features themselves.\n\nWith some effort on your part, you can significantly improve the adoption of\nyour product by making it easier to integrate and use. A smooth setup process\nnot only enhances user satisfaction but also reduces the barriers to bringing\nyour hardware to market.\n\n### Provide Simulators Alongside Your Hardware\n\nIf you supply hardware, consider providing a software simulator that mimics your\ndevice. This greatly simplifies integration into users' SIL/PIL/HIL setups,\nespecially if the target users have access to only a limited number of your\ndevices (such as when the device is very expensive).\n\nFor language choice, default to Python, as it is widely used for embedded\ndevelopment tools. If performance is critical, a C/C++/Rust simulator is also a\ngreat option, as these languages integrate well with embedded environments.\n\n## Documentation\n\n### The Illusion of Easy Documentation\n\nGood documentation is dry and boring. This can create an illusion that writing\ngood documentation is easy when in fact it is not.\n\n### Software Design Document\n\nNo matter the industry, team size, or type of project, every software team\nshould maintain a document that describes the design and architecture of the\ndeveloped system. This document may vary in size - from a few pages to a large,\ndetailed file - but its purpose is the same: to give any reader a high-level\nunderstanding of how the software is structured and how its key parts work\ntogether.\n\nA good approach is to organize it by topic or component, for example, one page\nper software component. Each section should clearly map to something real in the\nsoftware, and vice versa. This helps ensure that every major concept or building\nblock is documented, discoverable, and easy to reason about.\n\nThis kind of document might be called a Design Document, Architecture Document,\nDesign File, or similar, depending on the team or industry. Whatever the name,\nits role is the same: to serve as the central, navigable entry point into the\nsoftware's structure and design.\n\n### Less prose, more structure\n\nTechnical documentation is supposed to focus engineer's attention on achieving a\ngiven goal such as to build a specific system. It is easier to focus one's\nattention on things that have structure embedded in them compared to things that\nare hidden in several paragraphs of prose. Prose has no structure and that is\nwhy a reader has to do an extra exercise of creating an order out of what they\nare reading. If the documentation already has an order in it, the reader can\nspend less time for a mental reconstruction of the content and focus on the\ntechnical facts more easily.\n\nSome of the important tools that communicate order in technical documentation:\n\n- Document structure and table of contents\n- Diagrams\n- Tables.\n\n### Too Much Structure Overload\n\nExcessively deep nesting in documents or folder structures can hinder the\nunderstanding of the overall project or system structure, especially if the\nprinciples used for organizing the sections lack consistency. Ideally, a good\nstructure should be intuitive, or at the very least, the organizational\nprinciple should be easy to understand and mentally map, facilitating easier\nnavigation of the content.\n\n### Encyclopedic Document\n\nAn encyclopedic document is created over time as a collection of inputs from\nvarious ad hoc events, eventually becoming a generic repository of everything.\nThese documents often have complex, nested structures and lack a single\nconsistent narrative. Reading them feels more like going through a dictionary\nfrom A to Z rather than following a coherent story. This can make it difficult\nfor readers to stay engaged, which might explain why many people shy away from\nreading standards altogether.\n\nStandards or guidelines are often structured in this encyclopedic way, as they\naim to encompass all aspects of product development or organizational processes.\nSimilarly, requirements specifications can easily take on an encyclopedic form,\nmaking them hard to navigate and comprehend.\n\nWhen creating such documents, it's important to establish a guiding principle\nthat helps readers mentally map and navigate the content. Ideally, the document\nshould include a unifying narrative or story that makes it easier to follow,\neven if the underlying information is complex or diverse. A clear structure and\nlogical flow can transform an overwhelming collection of information into a\nuseful and accessible resource.\n\n## Meetings\n\n### Sound Check\n\nIt's great when everyone joins a meeting on time, but an often-overlooked\npractice is doing a quick sound and video check to ensure everything is working\nsmoothly. A good rule of thumb is to join:\n\n- 5 minutes early for routine meetings.\n- 15-30+ minutes early for important meetings, to handle any technical issues in\n  advance.\n\n### Meeting Agenda\n\nA well-prepared meeting runs smoothly when attendees know what to expect.\n\n- A strong meeting has a predefined agenda that allows participants to follow a\n  clear execution plan.\n- Is the agenda known in advance?\n- Can you or your team define it?\n- Are there questions or answers that can be prepared beforehand?\n\n### Meeting Notes\n\nMeetings often lack structure, and when no notes are taken, valuable discussions\ncan be lost. A better approach is for someone to take ownership of note-taking\nin real-time, ideally on a shared screen so everyone can see what is being\nrecorded.\n\n- If your team owns the agenda, align meeting notes with the planned topics.\n- Structure notes so key points and next steps are clear.\n\n### Capturing Meeting Results\n\nA meeting without tangible outcomes is just an expensive conversation. At a\nminimum, meetings should result in:\n\n- Action points: tasks, follow-ups, next meetings.\n- Decisions made.\n- Recognized trade-offs.\n\nWhenever possible, capturing processes or architectures in a diagram is better\nthan a simple bullet point. Even if no formal notes are recorded, every\nparticipant leaves with takeaways and mental models - but written records\nsignificantly increase the meeting's effectiveness.\n\nAnti-pattern: Running meetings without documenting useful outcomes, leading to\nwasted time and repeated discussions.\n\n### Briefing In\n\nBefore the actual meeting, getting alignment among participants is key, whether\nfor internal team discussions or external events like conferences and large\nreview meetings. When a team participates in an external meeting, it is crucial\nthat everyone is on the same page and presents a unified front, avoiding any\nvisible disagreement or misalignment.\n\nGood questions to determine if a pre-meeting briefing is needed:\n\n- How many attendees already know what will be presented?\n- Does the content introduce significant innovation that requires prior context?\n  Could too much new information create confusion within the presenting team?\n\nCommon pitfalls:\n\n- Discussing internal team matters in the presence of external participants.\n- Asking too many unrelated questions that derail the focus of the meeting,\n  particularly when it disrupts team cohesion and diverts attention from the\n  main agenda. This is especially problematic when an individual undermines the\n  shared position of the team by introducing misalignment.\n\n### Sharing Screen \u0026 Presenting Material\n\n- Share only the relevant content - close unrelated applications, especially\n  internal company chats, before presenting to an external audience.\n- If you need to access other files or perform actions outside the presentation,\n  unshare your screen first, complete the task, then reshare only the necessary\n  content.\n- If your team is presenting to an external party, align on the materials\n  beforehand to ensure consistency in messaging.\n\n## Systems\n\n### Good enough is often best\n\n\"Good enough for each part is often best for the whole system.\" (\"The Art of\nSystems Thinking\")\n\nIn \"Engineering a Safer World\", Nancy Leveson discusses how, in air traffic\ncontrol, individual flight paths may not be optimized for each aircraft to\nensure overall traffic harmony. This approach is necessary because optimizing\neach flight path individually could lead to conflicts and inefficiencies.\nInstead, air traffic control systems manage traffic by coordinating flight paths\nto maintain safe separation between aircraft, ensuring the overall safety and\nefficiency of the airspace.\n\n### Designing Systems for Effective Work\n\n- \"Rather than trying to find extraordinary people to do a job, design the job\n  so that ordinary people can do it well.\" (\"The Art of Systems Thinking\")\n\n\u003e ...No one comes to work to do a bad job, but the structure of the system may\n\u003e make good work impossible. If management falls into the blame trap, they may\n\u003e fire the offending individual and hire someone else - who may do no better.\n\u003e Rather than trying to find extraordinarypeople to do a job, design the job so\n\u003e that ordinary people can do it well. It is the structure of the system that\n\u003e creates the results. For better results, change the structure of the system.\n\n### The Risk of Default Outcomes\n\nUnresolved trade-offs, especially those that persist over long periods, can be\nrisky. Decisions left undecided, such as whether to build or buy critical\nhardware, will not remain open forever. Instead, they tend to resolve themselves\nby default, often in bad ways, whether due to inertia, external pressures, or\nshort-term needs. Like a coin that always falls on a side, an undecided\ntrade-off will eventually land on an outcome which might not align with\nstrategic goals.\n\nTo mitigate this risk, individuals, managers, teams, and organizations should\nproactively track and resolve open decisions, ensuring that critical choices are\nmade deliberately rather than by default. Tools such as an Open Questions Log or\na Risk Registry can support the structured resolution of such trade-offs.\n\n## People and Organizations\n\n### Everyone is busy\n\nEveryone is busy, including you. The development of software products often\ntakes place in rushed environments, where everyone is focused on achieving\nspecific goals without having time to do things properly or fully explore all\nthe options for what is being built.\n\nHow about QA? A company may have a dedicated QA department, or even Safety \u0026\nReliability teams in addition. They are most likely also busy, focusing on the\nmost critical tasks to the point that they probably don't have enough time to\ninteract with development teams, understand the real requirements, or provide\n100% coverage and a complete assessment of the project scope.\n\nIs it a problem that everyone is busy? Given its ubiquity, it doesn't seem so.\nSome people even seem to thrive on being busy all the time. Organizations appear\nto care little about \"busyness\" itself. What really matters is whether the busy\nperson or department can deliver results according to the schedule or whether\nsomething left uncovered by the busy teams could create serious problems for the\nbusiness.\n\nOne unfortunate observation is that it usually takes significant time before the\nuncovered issues are revealed and addressed from the top down. During this\nincubation period, enough money is often lost, a number of unhappy customers\naccumulate, and other losses may occur, depending on the type of project.\n\nOr, busy people themselves get tired... and create new methods and tools.\nSometimes, a new tool can eliminate much of the effort required to achieve a\ngoal, or it simply allows a busy person to focus on \"what is most important\"\nrather than covering everything.\n\n### Solving Problems with Cash\n\nEvery engineering problem can be solved with an infinite amount of cash.\n\n### The Paradox of Rushing in Software/Systems Engineering\n\nAttempting to accelerate development often leads to greater delays. In highly\ncomplex systems, skipping thorough validation, testing, or review processes can\nresult in unforeseen issues, requiring extensive rework and ultimately\nprolonging the timeline beyond what a steady, methodical approach would have\ntaken.\n\nThere is one [parable](https://howtopracticezen.org/Advanced%20Zen/) that sounds\nlike this:\n\n\u003e Zen teachers often tell the story of a young monk who asked a Zen master:\n\u003e\n\u003e \"How long will it take me to attain enlightenment?\" The master thought for a\n\u003e few moments and replied: \"About ten years.\" The young monk was upset and said:\n\u003e \"But you are assuming I am like the other monks, and I am not. I will practice\n\u003e with great determination.\" \"In that case\", replied the Master, \"twenty years.\"\n\nand a [similar one](https://martialarts.stackexchange.com/a/7133/7133):\n\n\u003e ... \"But if I work hard, how many years will it take to become a master?\"\n\u003e persisted the youth.\n\u003e\n\u003e \"Oh, maybe thirty years\", said Banzo.\n\u003e\n\u003e \"Why is that?\" asked Matajuro. \"First you say ten and now thirty years. I will\n\u003e undergo any hardship to master this art in the shortest time!\"\n\u003e\n\u003e \"Well\", said Banzo, \"in that case you will have to remain with me for seventy\n\u003e years. A man in such a hurry as you are to get results seldom learns quickly.\"\n\n### Four seasons\n\nIt is an amusing analogy: like a year starts with a spring and ends with a\nwinter, a similar lifecycle can be observed in a growth of organizations.\n\nSpring is a young company, a handful of people. Not much structure, no strict\npolicies, a startup atmosphere. Not yet a fixed income, but probably investments\nor lack of them. More full-stack people with broad expertise. Spring is like a\nvillage. Colleagues are fellow villagers.\n\nA Summer is a Spring that made it, a company that is flourishing. Exponential\ngrowth, more people are hired, extremely steep curve of everything: the\ndevelopment of the company structure, more departments, more specialization. The\nphilosophy of the company is no longer about \"finding its way\" but rather\naccelerating on what made a transition from Spring to Summer possible.\n\nAutumn is already a company with legacy. The source of income is known and\nstabilized. The responsibilities are defined. Less or no people are busy with\ndefining a product anymore but more people are busy with the optimization:\nimproving product, doing sales and increasing revenues.\n\nWinter is a dangerous phase. The company has been making profit and doing its\nbest by exhausting what was known to work well. At this point, the structure of\nthe company is the most fixed and therefore the least resilient. The company may\ncease to exist because there are younger and more adequate competitors or it can\nfind a way to renew itself and make it into a new year.\n\nAnother interesting observation is that a transition from season to season\nalmost never goes smoothly - in order to accomodate for change, the company has\nto adapt and this very often happens with a good deal of destruction and\nrestructuring (see Prima Materia heuristic). Dropping what does not work and\nkeeping or creating what does might be crucial for such a transition. Not all of\nthe Spring companies make it into Summer. Not all of the companies end up being\nWinter. Not all of the companies can survive their deep Winter.\n\nOne particular management mistake that can be made is trying to apply the best\npractices of a season A to a season B if the season B is too early or already\ntoo late for such an application. Example: imposing a strict top-down style of\nmanagement on a company of 5-10 people working in a flat hierarchy and making\nthem to adhere to the reporting lines might be extremely inadequate as well as\nexpecting a fully flat hierarchy to work in an Autumn-like business.\n\nNot only we can match seasons and companies, we can also match seasons and\npersonalities:\n\n- Autumn is too boring for spring people who value creativity and individual\n  contribution over hierarchies and defined processes.\n- For Autumn people, the Spring is too chaotic and unstructured. Working for a\n  Spring company is inherently unsafe: the younger the company, the less\n  guarantees it can provide to its employees.\n- It may not be optimal for a company to have too many people who represent an\n  incompatible season. It can be damaging for a person to get stuck working at a\n  company that does not match their season type. In such cases, a person who\n  found a matching season can be compared to a fish that found its water.\n\nSee also Kent Beck's\n[The Product Development Triathlon](https://medium.com/@kentbeck_7670/the-product-development-triathlon-6464e2763c46).\nHis 3 phases: Explore-Expand-Extract can be loosely mapped to the\nSpring-Summer-Autumn seasons.\n\n## Standards\n\n### Idealized standards vs. practical implementation\n\nStandards provide an idealized or encyclopedic view of how systems should\nfunction and how products should be developed. Frequently, a standard represents\nthe combined inputs of multiple companies, making it more extensive than what\nany single company might realistically implement. For most companies,\nimplementing a standard is a \"best effort\" exercise.\n\nSome standards are practical only for larger companies and can be\ncounterproductive or harmful for smaller organizations attempting to implement\nthem. Recognizing this, some standards explicitly account for a company's\nmaturity level and offer recommendations on which parts to implement at\ndifferent stages of development.\n\n### The challenge of standards implementation\n\nImplementing standards and managing their results within an organization can be\ndifficult and complex. However, without any standards, everything becomes 10 to\n100 times harder and more chaotic.\n\n### Standards and best practices\n\nStandards seek out best practices, collect them, and generalize them.\n\n### Standards favor good practice\n\nStandards favor good practices. If a company has adopted a practice that is not\nyet conventional but makes sense and adds value, it is unlikely that this\npractice would be rejected or deemed inappropriate by any standard.\n\n### Wrong is worse than early or incomplete\n\nSometimes it is worse to be wrong than to be early or lack information. The\ncontext: passing the project review milestones required by standards.\n\n## Requirements\n\n### One-stop shopping\n\n\u003e \"One-stop shopping\" is a useful requirements writing priciple. Simply, people\n\u003e reading the requirements should be able to get all the information they need\n\u003e from one document or from one section of a document. They should not have to\n\u003e jump between different sections to understand the requirement. (Patterns for\n\u003e Effective Use Cases by Steve Adolph et al., Chapter 7.1)\n\n## Safety\n\n### Safety does not exist without blood, loss or failure\n\nSafety is not there from the very beginning. A gloomy poet could say that safety\nblooms on blood. Safety does also not exist on its own: you first need to build\nsomething that kills people or causes a loss, then some people will bother to\nlearn from this and take actions. Only then safety gets recognized and truly\nappreciated.\n\nConsequence: safety is especially sound for those folks who have some experience\nof dealing with blood, loss or failure.\n\n### Safety is boring\n\nWhen implemented well enough, safety becomes boring. Everything is working, no\none complains. At that moment, it is easier than ever to forget about why the\nsafety is there in the first place. Example: how often do we bother to look at\nthe safety manuals? Does it mean that the safety is there?\n\n### Safety is very hard to achieve but is very easy to lose\n\nSafety is the extremely fragile and sensitive property of the systems. It so\nmuch effort that is put into achieving it and still it is so easy to let the\nwhole system get down. Some of the very popular reasons for the failure are:\n\n- degradation of existing components\n- changes to the system that do not take the current system's behavior into\n  account\n- new unexpected factors coming outside the system boundary\n\nConsequence: safety requires continuous and intelligent effort.\n\n### Success breeds failure\n\nHandbook of Walkthroughs, Inspections, and Technical Reviews, p.412:\n\n\u003e ... however, we have to anticipate that we will in fact succeed once in a\n\u003e while - and we must also anticipate what that success will bring. For\n\u003e instance, one error-riddled system was seldom used by its several hundred\n\u003e potential users, so management decided to mount an effort to have the system\n\u003e repaired in a systematic fashion. The resulting system was so dependable and\n\u003e useful that usage suddenly increased by a factor of a thousand over previous\n\u003e usage. This increase in transaction volume made the file design of the system\n\u003e completely inadequate to the daily load - which soon meant that nobody could\n\u003e get results fast enough to be useful. The entire problem - and so many others\n\u003e like it - could have been avoided if the review group had only considered that\n\u003e unavoidable law of nature: **Success breeds failure**. So, ..., be prepared\n\u003e for the inevitable reaction. If you start making systems better, your users\n\u003e will want more of the same - the best side effect of all.\n\n### Safety as a Defensive Discipline\n\nSafety is often seen as a defensive discipline, in contrast to fields focused on\ncreation, innovation, and action, which drive progress. While these fields push\nforward with new ideas and developments, safety functions as a secondary,\nbacking force. Its role is to prevent harm, minimize risks, and ensure that\nthese actions happen within a secure framework. Safety doesn't seek to lead the\ncharge but to protect and enable other processes to unfold without catastrophic\nfailure.\n\nHowever, the drive to \"lead the charge\" often means safety is ignored or\nsidelined until it's too late. In this way, safety acts like a belt that holds\nuncontrolled progress together, preventing it from falling apart when the\ninevitable risks are not properly addressed.\n\n### Safety for Engineering is Like Medicine for People\n\nMedicine isn't the most exciting thing, and no one wants to spend all their time\nthinking about it. But it's clear that humanity can't thrive without it, even\nwith all the amazing achievements of civilization.\n\nIn the same way, organizations focus on building things that work and often\ndon't think much about safety or quality as long as things are fine and\ncustomers are happy. But over time, they may realize that the \"health\" of their\nproducts, teams, and development processes also matters.\n\nHow safety and quality are handled depends a lot on experience and knowledge.\nNot long ago, amputation was seen as the best way to treat many illnesses. This\nshows how much we've learned and how practices improve over time. Engineering\nalso needs to grow in this way, moving beyond quick fixes to create stronger,\nlonger-lasting solutions.\n\n### User Interfaces and Critical Systems\n\nToo much simplicity can be a problem. Overly simplistic interfaces may prevent\noperators from engaging their brains fully, which could negatively impact their\nperformance in critical situations. If an interface is too simple, operators can\nfall into automatism, executing the wrong action due to a lack of alertness.\nThere are serious concerns that software and interface designers should\nprioritize preventing user mistakes, rather than focusing solely on aesthetics.\n\n## Books\n\n- [The Art of Systems Thinking](https://www.google.de/search?q=the+art+of+systems+thinking+book\u0026oq=the+art+of+systems+thinking+book)\n\n## Similar resources\n\n- [Kent Beck - Mastering Programming](https://www.facebook.com/notes/kent-beck/mastering-programming/1184427814923414/)\n- [Heuristics of Software Testability](http://www.satisfice.com/tools/testable.pdf)\n- [The Law of Leaky Abstractions](https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/)\n- [Lessons Learned in Software Development](https://henrikwarne.com/2015/04/16/lessons-learned-in-software-development/)\n\n## Copyright\n\nCopyright (c) 2015-2025 Stanislav Pankevich s.pankevich@gmail.com.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstanislaw%2Fnotes_on_software_work","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstanislaw%2Fnotes_on_software_work","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstanislaw%2Fnotes_on_software_work/lists"}