A place to cache linked articles (think custom and personal wayback machine)
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

index.html 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang="en">
  4. <!-- Has to be within the first 1024 bytes, hence before the `title` element
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset="utf-8">
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name="viewport" content="width=device-width,initial-scale=1">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>EffVer: Version your code by the effort required to upgrade (archive) — David Larlet</title>
  13. <meta name="description" content="Publication mise en cache pour en conserver une trace.">
  14. <!-- That good ol' feed, subscribe :). -->
  15. <link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/">
  16. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  17. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png">
  18. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png">
  19. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png">
  20. <link rel="manifest" href="/static/david/icons2/site.webmanifest">
  21. <link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c">
  22. <link rel="shortcut icon" href="/static/david/icons2/favicon.ico">
  23. <meta name="msapplication-TileColor" content="#f7f7f7">
  24. <meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml">
  25. <meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)">
  26. <meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)">
  27. <!-- Is that even respected? Retrospectively? What a shAItshow…
  28. https://neil-clarke.com/block-the-bots-that-feed-ai-models-by-scraping-your-website/ -->
  29. <meta name="robots" content="noai, noimageai">
  30. <!-- Documented, feel free to shoot an email. -->
  31. <link rel="stylesheet" href="/static/david/css/style_2021-01-20.css">
  32. <!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
  33. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  34. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  35. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  36. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  37. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  38. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  39. <script>
  40. function toggleTheme(themeName) {
  41. document.documentElement.classList.toggle(
  42. 'forced-dark',
  43. themeName === 'dark'
  44. )
  45. document.documentElement.classList.toggle(
  46. 'forced-light',
  47. themeName === 'light'
  48. )
  49. }
  50. const selectedTheme = localStorage.getItem('theme')
  51. if (selectedTheme !== 'undefined') {
  52. toggleTheme(selectedTheme)
  53. }
  54. </script>
  55. <meta name="robots" content="noindex, nofollow">
  56. <meta content="origin-when-cross-origin" name="referrer">
  57. <!-- Canonical URL for SEO purposes -->
  58. <link rel="canonical" href="https://jacobtomlinson.dev/effver/">
  59. <body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all">
  60. <article>
  61. <header>
  62. <h1>EffVer: Version your code by the effort required to upgrade</h1>
  63. </header>
  64. <nav>
  65. <p class="center">
  66. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  67. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  68. </svg> Accueil</a> •
  69. <a href="https://jacobtomlinson.dev/effver/" title="Lien vers le contenu original">Source originale</a>
  70. <br>
  71. Mis en cache le 2024-01-18
  72. </p>
  73. </nav>
  74. <hr>
  75. <p>Version numbers are hard to get right. Semantic Versioning <a href="https://semver.org/">(SemVer)</a> communicates backward compatibility via version numbers which often lead to a <a href="https://hynek.me/articles/semver-will-not-save-you/">false sense of security and broken promises</a>. Calendar Versioning <a href="https://calver.org/">(CalVer)</a> sits at the other extreme of communicating almost <a href="https://jacobtomlinson.dev/posts/2023/sometimes-i-regret-using-calver/">no useful information at all</a>.</p>
  76. <p>Going forward I plan to version the projects I work on in a way that communicates <em>how much effort I expect a user will need to spend to adopt the new version</em>. I’m going to refer to that scheme as <strong>Intended Effort Versioning (<span>EffVer</span> for short)</strong>.</p>
  77. <p>
  78. <figure>
  79. <img src="https://jacobtomlinson.dev/effver/effver.png" alt="In EffVer you use a three number version separated by dots, referred to as Macro, Meso and Micro. You incremenet macro when adoption requires a large effort, meso when it requires some effort and micro when hopefully it requires little to no effort.">
  80. <figcaption>Overview of EffVer</figcaption>
  81. </figure>
  82. </p>
  83. <p><span>EffVer</span> follows the same pattern of incrementing numbers to communicate with users that SemVer does, and is forward and backward compatible with SemVer (you don’t need to use something like a <a href="https://packaging.python.org/en/latest/specifications/version-specifiers/#version-epochs">Python version epoch</a> to switch between the two schemes). The difference is that instead of quantifying the orthogonality of a change EffVer tries to quantify the intended work required to adopt the change.</p>
  84. <p>If a change fixes a small bug, adds a new feature that is orthogonal with existing features or is generally a noop for existing users then you should bump the <strong>Micro</strong> version. This signals to users that <strong>“this change doesn’t intend for you to need to do anything”</strong>.</p>
  85. <p>If a change fixes a larger bug that some users may have grown accustom to or put workarounds in place for, or makes small breaking changes to features in a way that may require some adoption then you should bump the <strong>Meso</strong> version. This signals to users that <strong>“some small effort may be required to make sure this version works for you”</strong>.</p>
  86. <p>If you make a huge breaking change or overhaul some large aspect of your project you should bump the <strong>Macro</strong> version. This signals to users that <strong>“you will need to dedicate some significant time to upgrading to this version”</strong>.</p>
  87. <h2 id="why-use-span-stylecolor-0097a7effverspan">Why use <span>EffVer</span>?</h2>
  88. <p><span>EffVer</span> may sound like “SemVer-lite” or just “SemVer done a certain way” but there are a few key things that makes <span>EffVer</span> different and worth considering.</p>
  89. <ol>
  90. <li><span>EffVer</span> communicates intentions. Software is created by humans (for now) and that while humans have the best of intentions around the impacts that new versions have, sometimes things are more impactful than expected. Instead of trying to quantify the techinical scope of a change <span>EffVer</span> tries to communicate the expected downstream impact.</li>
  91. <li><span>EffVer</span> respects that all releases impact users and will require effort to adopt them, even if that’s some simple testing or updating a lock file. By trying to quantify and communicate the effort required to adopt a release developers demonstrate respect for their user’s time.</li>
  92. <li><span>EffVer</span> doesn’t make a distinction between bugs fixes, enhancements and features (because developers struggle to make that distinction too). Instead we focus only on the effort required for existing users to adopt new versions.</li>
  93. <li><span>EffVer</span> users can more clearly reason that <em>any</em> change can result in them needing to do some work, but that the developer using <span>EffVer</span> is trying to give them information to help them quantify and plan this work.</li>
  94. </ol>
  95. <h2 id="fixing-mistakes">Fixing mistakes</h2>
  96. <p>Another core principle of <span>EffVer</span> is to acknowledge that sometimes code gets released with the wrong version number, and responsible developers should reactively fix that.</p>
  97. <p>Imagine 1% of my users are experiencing a bug, so I make a bug fix release where I intend for only those users notice that positive change. So I increment the <em>micro</em> version number and cut a release.</p>
  98. <p>However I was wrong, other users are negatively impacted by the change and have to make a small adjustment to their workflow to work around it. On reflection I am still happy with the change and don’t intend to revert it, but I should’ve incremented the <em>meso</em> version number instead to signal a larger impact. I made the change that I wanted to make, but I have accidentally generated work for others and I should respectfully communicate that to them.</p>
  99. <p>With <span>EffVer</span> we encourage developers to take some steps to update the communicated impact by cutting a few more releases.</p>
  100. <ul>
  101. <li>Imagine my starting point was version <code>2.3.4</code>.</li>
  102. <li>My bug fix was then released as <code>2.3.5</code>.</li>
  103. <li>The unhappy users open issues on GitHub and I want to change the version number to communicate the impact better.</li>
  104. <li>I check out the original <code>2.3.4</code> tag and create another new tag for this commit called <code>2.3.6</code>, this effectively reverts the impactful release so that no more users pick up the change.</li>
  105. <li>Then I check out <code>2.3.5</code>, the impactful change, and create a new tag called <code>2.4.0</code>.</li>
  106. </ul>
  107. <p>Now users who have pinned to <code>~2.3.4</code> will be upgraded to <code>2.3.6</code> which is exactly the same commit and therefore doesn’t cause them any impact. And users who have pinned to <code>^2.3.4</code> will be upgraded to <code>2.4.0</code> which correctly communicates that there may be some small intentional impact.</p>
  108. <p>I haven’t needed to change my code or make any new commits, I just add more tags to fix my communication.</p>
  109. <h2 id="zero-version">Zero version</h2>
  110. <p>In SemVer the <code>0.x.x</code> version has become known as the YOLO version because anything goes. Any change can be breaking and so the semantics around backward compatibility become meaningless.</p>
  111. <p>In <span>EffVer</span> the meaning of the zero version still denotes a codebase under development but should be treated as <code>0.Macro.Micro</code>. In a development project it is more likely that changes will have a large impact, that’s just in their nature, but it’s still useful to be able to quantify the impact between each release.</p>
  112. <p>You could also use a four segment version number with <code>0.Macro.Meso.Micro</code> if you would prefer to have the full fidelity of <span>EffVer</span> communication during development.</p>
  113. <p>As your project matures you will likely find yourself incrementing the <em>Macro</em> version less and the <em>Micro</em> version more which is a good signal for developers that it’s time to switch to a <code>1.0.0</code> release.</p>
  114. <h2 id="projects-using-span-stylecolor-0097a7effverspan">Projects using <span>EffVer</span></h2>
  115. <p>Here are some notable projects that use EffVer:</p>
  116. <p><em>Want to add your project to this list, <a href="https://github.com/jacobtomlinson/website/blob/master/content/posts/2024/2024-01-15-effver/index.md">make a PR here</a>.</em></p>
  117. <h2 id="supporting-span-stylecolor-0097a7effverspan">Supporting <span>EffVer</span></h2>
  118. <p>Do you like the sound of <span>EffVer</span>? If so that’s great! You can support the movement by sharing this post with people and by adding the <a href="https://jacobtomlinson.dev/effver">
  119. <img src="https://img.shields.io/badge/version_scheme-EffVer-0097a7" alt="Static Badge">
  120. </a> badge to any projects that are using it.</p>
  121. <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl"><span class="gh"># Badge URL
  122. </span></span></span><span class="line"><span class="cl"><span class="gh"></span>https://img.shields.io/badge/version_scheme-EffVer-0097a7
  123. </span></span><span class="line"><span class="cl">
  124. </span></span><span class="line"><span class="cl"><span class="gh"># Markdown
  125. </span></span></span><span class="line"><span class="cl"><span class="gh"></span>[<span class="nt">![EffVer Versioning</span>](<span class="na">https://img.shields.io/badge/version_scheme-EffVer-0097a7</span>)](https://jacobtomlinson.dev/effver)
  126. </span></span></code></pre></div>
  127. <h2 id="background-and-history">Background and history</h2>
  128. <p>At PyCon UK in 2023 I gave a lightning talk based on my blog post <a href="https://jacobtomlinson.dev/posts/2023/sometimes-i-regret-using-calver/">“Sometimes I regret using CalVer”</a>. My talk was immediately followed by <a href="https://hynek.me/about/">Hynek Schlawack</a> who aside from creating great Python libraries like <a href="https://github.com/python-attrs/attrs"><code>attrs</code></a> and <a href="https://github.com/hynek/structlog"><code>structlog</code></a> is known for his blog post <a href="https://hynek.me/articles/semver-will-not-save-you/">“Semantic Versioning Will Not Save You”</a>.</p>
  129. <p>Interestingly many folks assumed that our dislike of different version schemes meant we vehemently disagreed with each other, but far from it. We totally agreed that the two most popular versioning schemes were imperfect and this resulted in some excellent post-conference pub discussion.</p>
  130. <p>Ever since then I’ve not been able to stop thinking “there has to be another option”.</p>
  131. <h3 id="the-challenges-of-existing-schemes">The challenges of existing schemes</h3>
  132. <p>Both my and Hynek’s blog posts go into detail about the failings of existing schemes, but I want to focus on the attributes that translate to work required by downstream users.</p>
  133. <h4 id="semver">SemVer</h4>
  134. <p>SemVer attempts to communicate if an upgrade is safe or not, but can easily get this wrong.</p>
  135. <p>When you fix a bug in your code you can argue that the code is now “more correct”. SemVer assumes it is safe for <em>everyone</em> to adopt this new code immediately because of this increased “correctness”, but the trap that SemVer falls into is the fact that every bug has users.</p>
  136. <p>
  137. <figure>
  138. <img src="https://jacobtomlinson.dev/effver/xkcd-1172-workflow.png" alt="xkcd 1172: workflow. A comic strip showing a user who is upset that holding the space bar no longer makes their computer overheat because they relied on that behaviour">
  139. <figcaption><a href="https://xkcd.com/1172">xkcd #1172: Workflow</a></figcaption>
  140. </figure>
  141. </p>
  142. <p>People trust SemVer to not break their code and then feel angry when things go wrong because when SemVer fails users have to react and often have urgent work to do.</p>
  143. <h4 id="calver">CalVer</h4>
  144. <p>CalVer attempts to communicate that no upgrade is safe, but in doing so strips all useful information from the version number.</p>
  145. <p>For example if you fix a small bug in your code and make a release, then the next day you delete half the API and make another release, nobody can tell the difference between the two versions.</p>
  146. <p>
  147. <figure>
  148. <img src="https://jacobtomlinson.dev/effver/happy-new-year.png" alt="A meme showing two scary dragons and a derpy one to describe major version releases. The first is ChangeVer and says there are major new and exciting things, the second is SemVer and says we broke something and the third is CalVer which says happy new year.">
  149. <figcaption>If you're wondering what ChangeVer is, it's what I call the 90s boxed software version scheme where you were obliged to make visible changes to your prouct and increment the major version in order to get people to upgrade from the old version. Change for the sake of change.</figcaption>
  150. </figure>
  151. </p>
  152. <p>People feel anxious about upgrading CalVer projects because they don’t know if the change will be small or huge. As a result they are more likely to pin their dependencies and upgrade in a more proactive and managed way, which is good, but the lack of information makes upgrading hard to schedule and so it often gets put off.</p>
  153. <p>If you read the CalVer website they <a href="https://calver.org/#ubuntu">highlight Ubuntu</a> as a high-profile user of CalVer. However, Ubuntu has shoehorned in a bunch of semantics to their versioning scheme by only creating April and October releases to make it clearer to users which versions are <em>major</em> versions. They wanted to communicate which versions take more effort to migrate between because communicating user impact is important.</p>
  154. <h3 id="momentum">Momentum</h3>
  155. <p>The biggest challenge for switching version scheme is the momentum of other schemes in the ecosystem. SemVer is well established, and CalVer is also very common. Because of that <span>EffVer</span> is intentionally identical in structure to SemVer. This means that any tooling or process assumptions built around SemVer will work for <span>EffVer</span>.</p>
  156. <p>Any SemVer project can switch to <span>EffVer</span> by just changing how they decide the version number of the next release. If you try <span>EffVer</span> out and would prefer to go back to traditional semantics then switching back is also just the same process change.</p>
  157. <p>Switching to CalVer is more of a one way street, and although some languages have a <a href="https://packaging.python.org/en/latest/specifications/version-specifiers/#version-epochs">process for switching back</a> it’s not guaranteed to be a smooth ride. So if you ever do switch to CalVer can I suggest you use <code>YY.MM.DD</code> instead of <code>YYYY.MM.DD</code>, that way you could switch back to <span>EffVer</span>/SemVer and keep your <em>major</em> version number below 100.</p>
  158. </article>
  159. <hr>
  160. <footer>
  161. <p>
  162. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  163. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  164. </svg> Accueil</a> •
  165. <a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2">
  166. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-rss2"></use>
  167. </svg> Suivre</a> •
  168. <a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie">
  169. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-user-tie"></use>
  170. </svg> Pro</a> •
  171. <a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail">
  172. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-mail"></use>
  173. </svg> Email</a> •
  174. <abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2">
  175. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-hammer2"></use>
  176. </svg> Légal</abbr>
  177. </p>
  178. <template id="theme-selector">
  179. <form>
  180. <fieldset>
  181. <legend><svg class="icon icon-brightness-contrast">
  182. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-brightness-contrast"></use>
  183. </svg> Thème</legend>
  184. <label>
  185. <input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
  186. </label>
  187. <label>
  188. <input type="radio" value="dark" name="chosen-color-scheme"> Foncé
  189. </label>
  190. <label>
  191. <input type="radio" value="light" name="chosen-color-scheme"> Clair
  192. </label>
  193. </fieldset>
  194. </form>
  195. </template>
  196. </footer>
  197. <script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script>
  198. <script>
  199. function loadThemeForm(templateName) {
  200. const themeSelectorTemplate = document.querySelector(templateName)
  201. const form = themeSelectorTemplate.content.firstElementChild
  202. themeSelectorTemplate.replaceWith(form)
  203. form.addEventListener('change', (e) => {
  204. const chosenColorScheme = e.target.value
  205. localStorage.setItem('theme', chosenColorScheme)
  206. toggleTheme(chosenColorScheme)
  207. })
  208. const selectedTheme = localStorage.getItem('theme')
  209. if (selectedTheme && selectedTheme !== 'undefined') {
  210. form.querySelector(`[value="${selectedTheme}"]`).checked = true
  211. }
  212. }
  213. const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
  214. window.addEventListener('load', () => {
  215. let hasDarkRules = false
  216. for (const styleSheet of Array.from(document.styleSheets)) {
  217. let mediaRules = []
  218. for (const cssRule of styleSheet.cssRules) {
  219. if (cssRule.type !== CSSRule.MEDIA_RULE) {
  220. continue
  221. }
  222. // WARNING: Safari does not have/supports `conditionText`.
  223. if (cssRule.conditionText) {
  224. if (cssRule.conditionText !== prefersColorSchemeDark) {
  225. continue
  226. }
  227. } else {
  228. if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
  229. continue
  230. }
  231. }
  232. mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
  233. }
  234. // WARNING: do not try to insert a Rule to a styleSheet you are
  235. // currently iterating on, otherwise the browser will be stuck
  236. // in a infinite loop…
  237. for (const mediaRule of mediaRules) {
  238. styleSheet.insertRule(mediaRule.cssText)
  239. hasDarkRules = true
  240. }
  241. }
  242. if (hasDarkRules) {
  243. loadThemeForm('#theme-selector')
  244. }
  245. })
  246. </script>
  247. </body>
  248. </html>