.. vim: syntax=rst Managing dependencies ********************************** Adding a dependency on existing code seems like a simple decision. Don’t repeat yourself (DRY) is a commonly taught principle. Why should we all write our own left-pad? Database drivers, application frameworks, machine learning packages—there are many examples of libraries you should not write from scratch. But dependencies bring risk: incompati­ble changes, circular dependencies, version conflicts, and lack of control. You must consider these risks and how to mitigate them. Dependency Management Basics ----------------------------- A *dependency* is code that your code relies on. The time at which a dependency is needed—during compilation, testing, or runtime—is called its *scope*. A good versioning scheme has versions that are: - **UNIQUE** Versions should never be reused. Artifacts get distrib­uted, cached, and pulled by automated workflows. Never republish changed code under an existing version. - **COMPARABLE** Versions should help humans and tools reason about version precedence. *Precedence* is used to resolve conflicts when a build depends on multiple versions of the same artifact. - **INFORMATIVE** Versions differentiate between prereleased and released code, associate build numbers to artifacts, and set stability and compatibility expectations. Semantic Versioning --------------------- (See for details: https://semver.org/) Given a version number MAJOR.MINOR.PATCH, increment the: 1. MAJOR version when you make incompatible API changes, 2. MINOR version when you add functionality in a backwards compatible manner, and 3. PATCH version when you make backwards compatible bug fixes. .. note:: **Prerelease** Major version 0, considered “prerelease,” is intended for fast iteration. SemVer also defines prerelease versions by appending a - character after the patch version. Dot­separated alphanumeric sequences are used for prerelease identifiers (2.13.7-alpha.2). **Release candidates** Many projects use *release candidate (RC)* builds. Early adopters can find bugs in RCs before the official release. RC prerelease versions have incremental identifiers, such as 3.0.0-rc.1. The final RC is then promoted to the release version by re-releasing it without an RC suffix. All prereleased versions are super­seded by the final release (3.0.0 in our example). **Build number** Build numbers are appended after both the version and prerelease metadata: 2.13.7­alpha.2+1942. Transitive Dependencies ------------------------ Dependencies usually depend on other libraries, which become *transitive dependencies*. A dependency report shows the fully resolved *dependency tree* (or *dependency graph*). Avoiding Dependency Hell ------------------------- Ask yourself if a dependency’s value outweighs its cost. - Do you really need the functionality? - How well maintained is the dependency? - How easy would it be for you to fix the dependency if something went wrong? - How mature is the dependency? - How frequently does the dependency introduce backward­incompatible changes? - How well do you, your team, and your organization understand the dependency? - How easy is it to write the code yourself? - How is the code licensed? - What is the ratio of code you use versus code you don’t use in the dependency? Use these tips to avoid dependency hell. **Isolate Dependencies:** Copy paste code, shade the dependency in another namespace **Deliberately Add Dependencies:** Explicitly declare as dependencies all libraries you use **Pin Versions:** **Scope Dependencies Narrowly:** Prod, dev, testing etc. **Protect Yourself from Circular Dependencies.** .. note:: **Sources:** 1. The Missing README: A Guide for the New Software Engineer © 2021 by Chris Riccomini and Dmitriy Ryaboy, Chapter 5.