A place to cache linked articles (think custom and personal wayback machine)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.html 34KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang="fr">
  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>Coping strategies for the serial project hoarder (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. <!-- Documented, feel free to shoot an email. -->
  28. <link rel="stylesheet" href="/static/david/css/style_2021-01-20.css">
  29. <!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
  30. <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>
  31. <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>
  32. <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>
  33. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  34. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  35. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  36. <script>
  37. function toggleTheme(themeName) {
  38. document.documentElement.classList.toggle(
  39. 'forced-dark',
  40. themeName === 'dark'
  41. )
  42. document.documentElement.classList.toggle(
  43. 'forced-light',
  44. themeName === 'light'
  45. )
  46. }
  47. const selectedTheme = localStorage.getItem('theme')
  48. if (selectedTheme !== 'undefined') {
  49. toggleTheme(selectedTheme)
  50. }
  51. </script>
  52. <meta name="robots" content="noindex, nofollow">
  53. <meta content="origin-when-cross-origin" name="referrer">
  54. <!-- Canonical URL for SEO purposes -->
  55. <link rel="canonical" href="https://simonwillison.net/2022/Nov/26/productivity/">
  56. <body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all">
  57. <article>
  58. <header>
  59. <h1>Coping strategies for the serial project hoarder</h1>
  60. </header>
  61. <nav>
  62. <p class="center">
  63. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  64. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  65. </svg> Accueil</a> •
  66. <a href="https://simonwillison.net/2022/Nov/26/productivity/" title="Lien vers le contenu original">Source originale</a>
  67. </p>
  68. </nav>
  69. <hr>
  70. <p>I gave a talk at DjangoCon US 2022 in San Diego last month about productivity on personal projects, titled “Massively increase your productivity on personal projects with comprehensive documentation and automated tests”.</p>
  71. <p>The alternative title for the talk was <em>Coping strategies for the serial project hoarder</em>.</p>
  72. <p>I’m maintaining a <em>lot</em> of different projects at the moment. Somewhat unintuitively, the way I’m handling this is by scaling down techniques that I’ve seen working for large engineering teams spread out across multiple continents.</p>
  73. <p>The key trick is to ensure that every project has comprehensive documentation and automated tests. This scales my productivity horizontally, by freeing me up from needing to remember all of the details of all of the different projects I’m working on at the same time.</p>
  74. <p>You can watch the talk <a href="https://www.youtube.com/watch?v=GLkRK2rJGB0">on YouTube</a> (25 minutes). Alternatively, I’ve included a detailed annotated version of the slides and notes below.</p>
  75. <p class="resp-container">
  76. <iframe allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="allowfullscreen" frameborder="0" src="https://www.youtube.com/embed/GLkRK2rJGB0">VIDEO</iframe>
  77. </p>
  78. <div class="slide">
  79. <img alt="Title slide: Massively increase your productivity on personal projects with comprehensive documentation and automated tests - Simon Willison, DjangoCon US 2022" loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.001.jpeg"><div>
  80. <p>This was the title I originally submitted to the conference. But I realized a better title was probably...</p>
  81. </div>
  82. </div>
  83. <div class="slide">
  84. <img alt="Same title slide, but the title has been replaced" loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.003.jpeg"><div>
  85. <p>Coping strategies for the serial project hoarder</p>
  86. </div>
  87. </div>
  88. <div class="slide">
  89. <img alt="A static frame from a video: a monkey sits on some steps stuffing itself with several pastries. In the longer video the monkey is handed more and more pastries and can't resist trying to hold and eat all of them at once, no matter how many it receives." loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.004.jpeg"><div>
  90. <p><a href="https://twitter.com/devisridhar/status/1576170527882121217">This video</a> is a neat representation of my approach to personal projects: I always have a few on the go, but I can never resist the temptation to add even more.</p>
  91. </div>
  92. </div>
  93. <div class="slide">
  94. <img alt="A screenshot of my profile on PyPI - my join date is Oct 26, 2017 and I have 185 pojects listed." loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.005.jpeg"><div>
  95. <p><a href="https://pypi.org/user/simonw/">My PyPI profile</a> (which is only five years old) lists 185 Python packages that I’ve released. Technically I’m actively maintaining all of them, in that if someone reports a bug I’ll push out a fix. Many of them receive new releases at least once a year.</p>
  96. <p>Aside: I took this screenshot using <a href="https://shot-scraper.datasette.io/">shot-scraper</a> with a little bit of extra JavaScript to hide a notification bar at the top of the page:</p>
  97. <div class="highlight highlight-source-shell"><pre>shot-scraper <span class="pl-s"><span class="pl-pds">'</span>https://pypi.org/user/simonw/<span class="pl-pds">'</span></span> \
  98. --javascript <span class="pl-s"><span class="pl-pds">"</span></span>
  99. <span class="pl-s"> document.body.style.paddingTop = 0;</span>
  100. <span class="pl-s"> document.querySelector(</span>
  101. <span class="pl-s"> '#sticky-notifications'</span>
  102. <span class="pl-s"> ).style.display = 'none';</span>
  103. <span class="pl-s"> <span class="pl-pds">"</span></span> --height 1000</pre></div>
  104. </div>
  105. </div>
  106. <div class="slide">
  107. <img alt="A map of the world with the Eventbrite logo overlaid on it. There are pins on San Francisco, Nashville, Mendoza and Madrid." loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.006.jpeg"><div>
  108. <p>How can one individual maintain 185 projects?</p>
  109. <p>Surprisingly, I’m using techniques that I’ve scaled down from working at a company with hundreds of engineers.</p>
  110. <p>I spent seven years at Eventbrite, during which time the engineering team grew to span three different continents. We had major engineering centers in San Francisco, Nashville, Mendoza in Argentina and Madrid in Spain.</p>
  111. <p>Consider timezones: engineers in Madrid and engineers in San Francisco had almost no overlap in their working hours. Good asynchronous communication was essential.</p>
  112. <p>Over time, I noticed that the teams that were most effective at this scale were the teams that had a strong culture of documentation and automated testing.</p>
  113. <p>As I started to work on my own array of smaller personal projects, I found that the same discipline that worked for large teams somehow sped me up, when intuitively I would have expected it to slow me down.</p>
  114. </div>
  115. </div>
  116. <div class="slide">
  117. <img alt="The perfect commit: Implementation + tests + documentation and a link to an issue thread" loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.007.jpeg"><div>
  118. <p>I wrote an extended description of this in <a href="https://simonwillison.net/2022/Oct/29/the-perfect-commit/">The Perfect Commit</a>.</p>
  119. <p>I’ve started structuring the majority of my work in terms of what I think of as “the perfect commit”—a commit that combines implementation, tests, documentation and a link to an issue thread.</p>
  120. <p>As software engineers, it’s important to note that our job generally isn’t to write new software: it’s to make changes to existing software.</p>
  121. <p>As such, the commit is our unit of work. It’s worth us paying attention to how we cen make our commits as useful as possible.</p>
  122. </div>
  123. </div>
  124. <div class="slide">
  125. <img alt="Screenshot of a commit on GitHub: the title is Async support for prepare_jinja2_environment, closes #1809" loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.008.jpeg"><div>
  126. <p>Here’s <a href="https://github.com/simonw/datasette/commit/ddc999ad1296e8c69cffede3e367dda059b8adad">a recent example</a> from one of my projects, Datasette.</p>
  127. <p>It’s a single commit which bundles together the implementation, some related documentation improvements and the tests that show it works. And it links back to an issue thread from the commit message.</p>
  128. <p>Let’s talk about each component in turn.</p>
  129. </div>
  130. </div>
  131. <div class="slide">
  132. <img alt="Implementation: it should just do one thing (thing here is deliberately vague)" loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.009.jpeg"><div>
  133. <p>There’s not much to be said about the implementation: your commit should change something!</p>
  134. <p>It should only change one thing, but what that actually means varies on a case by case basis.</p>
  135. <p>It should be a single change that can be documented, tested and explained independently of other changes.</p>
  136. <p>(Being able to cleanly revert it is a useful property too.)</p>
  137. </div>
  138. </div>
  139. <div class="slide">
  140. <img alt="Tests: prove that the implementation works. Pass if the new implementation is correct, fail otherwise." loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.010.jpeg"><div>
  141. <p>The goals of the tests that accompany a commit are to prove that the new implementation works.</p>
  142. <p>If you apply the implementation the new tests should pass. If you revert it the tests should fail.</p>
  143. <p>I often use <code>git stash</code> to try this out.</p>
  144. <p>If you tell people they need to write tests for <em>every single change</em> they’ll often push back that this is too much of a burden, and will harm their productivity.</p>
  145. </div>
  146. </div>
  147. <div class="slide">
  148. <img alt="Every project should start with a test. assert 1 + 1 == 2 is fine! Adding tests to an existing test suite is SO MUCH less work than starting a new test suite from scratch." loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.011.jpeg"><div>
  149. <p>But I find that the incremental cost of adding a test to an existing test suite keeps getting lower over time.</p>
  150. <p>The hard bit of testing is getting a testing framework setup in the first place—with a test runner, and fixtures, and objects under test and suchlike.</p>
  151. <p>Once that’s in place, adding new tests becomes really easy.</p>
  152. <p>So my personal rule is that every new project starts with a test. It doesn’t really matter what that test does—what matters is that you can run <code>pytest</code> to run the tests, and you have an obvious place to start building more of them.</p>
  153. </div>
  154. </div>
  155. <div class="slide">
  156. <img alt="Cookiecutter repo templates: simonw/python-lib, simonw/click-app, simonw/datasette-plugin" loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.012.jpeg"><div>
  157. <p>I maintain three <a href="https://cookiecutter.readthedocs.io/">cookiecutter</a> templates to help with this, for the three kinds of projects I most frequently create:</p>
  158. <p>Each of these templates creates a project with a <code>setup.py</code> file, a README, a test suite and GitHub Actions workflows to run those tests and ship tagged releases to PyPI.</p>
  159. </div>
  160. </div>
  161. <div class="slide">
  162. <img alt="Screenshot of the GitHub page to create a new repsoitory from python-lib-template-repository, which asks for a repository name, a description string and if the new repo should be public or private." loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.013.jpeg">
  163. </div>
  164. <div class="slide">
  165. <img alt="Documentation: Same repository as the code! Document changes that impact external developers. Update the docs in the same commit as the change. Catch missing documentation updates in PR / code review" loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.014.jpeg"><div>
  166. <p>This is a hill that I will die on: your documentation must live in the same repository as your code!</p>
  167. <p>You often see projects keep their documentation somewhere else, like in a wiki.</p>
  168. <p>Inevitably it goes out of date. And my experience is that if your documentation is out of date people will lose trust in it, which means they’ll stop reading it and stop contributing to it.</p>
  169. <p>The gold standard of documentation has to be that it’s reliably up to date with the code.</p>
  170. <p>The only way you can do that is if the documentation and code are in the same repository.</p>
  171. <p>This gives you versioned snapshots of the documentation that exactly match the code at that time.</p>
  172. <p>More importantly, it means you can enforce it through code review. You can say in a PR “this is great, but don’t forget to update this paragraph on this page of the documentation to reflect the change you’re making”.</p>
  173. <p>If you do this you can finally get documentation that people learn to trust over time.</p>
  174. </div>
  175. </div>
  176. <div class="slide">
  177. <img alt="Bonus trick: documentation unit tests" loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.015.jpeg"><div>
  178. <p>Another trick I like to use is something I call documentation unit tests.</p>
  179. <p>The idea here is to use unit tests to enforce that concepts introspected from your code are at least mentioned in your documentation.</p>
  180. <p>I wrote more about that in <a href="https://simonwillison.net/2018/Jul/28/documentation-unit-tests/">Documentation unit tests</a>.</p>
  181. </div>
  182. </div>
  183. <div class="slide">
  184. <img alt="Screenshot showing pytest running 26 passing tests, each with a name like test_plugin_hook_are_documented[filters_from_request]" loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.016.jpeg"><div>
  185. <p>Here’s an example. Datasette has <a href="https://github.com/simonw/datasette/blob/0.63.1/tests/test_docs.py#L41-L53">a test</a> that scans through each of the Datasette plugin hooks and checks that there is a heading for each one in the documentation.</p>
  186. </div>
  187. </div>
  188. <div class="slide">
  189. <img alt="Screenshot of the code linked to above" loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.017.jpeg"><div>
  190. <p>The test itself is pretty simple: it uses <code>pytest</code> parametrization to look through every introspected plugin hook name, and for each one checks that it has a matching heading in the documentation.</p>
  191. </div>
  192. </div>
  193. <div class="slide">
  194. <img alt="Everything links to an issue thread" loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.018.jpeg"><div>
  195. <p>The final component of my perfect commit is this: every commit must link to an issue thread.</p>
  196. <p>I’ll usually have these open in advance but sometimes I’ll open an issue thread just so I can close it with a commit a few seconds later!</p>
  197. </div>
  198. </div>
  199. <div class="slide">
  200. <img alt="A screenshot of the issue titled prepare_jinja_enviroment() hook should take datasette argument - it has 11 comments" loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.019.jpeg"><div>
  201. <p>Here’s <a href="https://github.com/simonw/datasette/issues/1809">the issue</a> for the commit I showed earlier. It has 11 comments, and every single one of those comments is by me.</p>
  202. <p>I have literally thousands of issues on GitHub that look like this: issue threads that are effectively me talking to myself about the changes that I’m making.</p>
  203. <p>It turns out this a fantastic form of additional documentation.</p>
  204. </div>
  205. </div>
  206. <div class="slide">
  207. <img alt="What goes in an issue?" loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.020.jpeg"><div>
  208. <p>What goes in an issue?</p>
  209. <ul>
  210. <li>Background: the reasons for the change. In six months time you’ll want to know why you did this.</li>
  211. <li>State of play before-hand: embed existing code, link to existing docs. I like to start my issues with “I’m going to change this code right here”—that way if I come back the next day I don’t have to repeat that little piece of research.</li>
  212. <li>Links to things! Documentation, inspiration, clues found on StackOverflow. The idea is to capture all of the loose information floating around that topic.</li>
  213. <li>Code snippets illustrating potential designs and false-starts.</li>
  214. <li>Decisions. What did you consider? What did you decide? As programmers we make decisions constantly, all day, about everything. That work doesn’t have to be invisible. Writing them down also avoids having to re-litigate them several months later when you’ve forgotten your original reasoning.</li>
  215. <li>Screenshots—of everything! Animated screenshots even better. I even take screenshots of things like the AWS console to remind me what I did there.</li>
  216. <li>When you close it: a link to the updated documentation and demo</li>
  217. </ul>
  218. </div>
  219. </div>
  220. <div class="slide">
  221. <img alt="Temporal documentation. It's timestamped and contextual. You don't need to commit to keeping it up-to-date in the future (but you can add more comments if you like)" loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.021.jpeg"><div>
  222. <p>The reason I love issues is that they’re a form of documentation that I think of as <em>temporal documentation</em>.</p>
  223. <p>Regular documentation comes with a big commitment: you have to keep it up to date in the future.</p>
  224. <p>Issue comments skip that commitment entirely. They’re displayed with a timestamp, in the context of the work you were doing at the time.</p>
  225. <p>No-one will be upset or confused if you fail to keep them updated to match future changes.</p>
  226. <p>So it’s a commitment free form of documentation, which I for one find incredibly liberating.</p>
  227. </div>
  228. </div>
  229. <div class="slide">
  230. <img alt="Issue driven development" loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.022.jpeg"><div>
  231. <p>I think of this approach as <em>issue driven development</em>.</p>
  232. <p>Everything you are doing is issue-first, and from that you drive the rest of the development process.</p>
  233. </div>
  234. </div>
  235. <div class="slide">
  236. <img alt="Don't remember anything: you can go back to a project in six months and pick up right where you left off" loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.023.jpeg"><div>
  237. <p>This is how it relates back to maintaining 185 projects at the same time.</p>
  238. <p>With issue driven development you <em>don’t have to remember anything</em> about any of these projects at all.</p>
  239. <p>I’ve had issues where I did a bunch of design work in issue comments, then dropped it, then came back 12 months later and implemented that design—without having to rethink it.</p>
  240. <p>I’ve had projects where I forgot that the project existed entirely! But I’ve found it again, and there’s been an open issue, and I’ve been able to pick up work again.</p>
  241. <p>It’s a way of working where you treat it like every project is going to be maintained by someone else, and it’s the classic cliche here that the somebody else is you in the future.</p>
  242. <p>It horizontally scales you and lets you tackle way more interesting problems.</p>
  243. <p>Programmers always complain when you interrupt them—there’s this idea of “flow state” and that interrupting a programmer for a moment costs them half an hour in getting back up to speed.</p>
  244. <p>This fixes that! It’s much easier to get back to what you are doing if you have an issue thread that records where you’ve got to.</p>
  245. <p>Issue driven development is my key productivity hack for taking on much more ambitious projects in much larger quantities.</p>
  246. </div>
  247. </div>
  248. <div class="slide">
  249. <img alt="Laboratory notebooks - and a picture of a page from one by Leonardo da Vinci" loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.024.jpeg"><div>
  250. <p>Another way to think about this is to compare it to laboratory notebooks.</p>
  251. <p>Here’s <a href="https://en.wikipedia.org/wiki/Studies_of_the_Fetus_in_the_Womb">a page</a> from one by Leonardo da Vinci.</p>
  252. <p>Great scientists and great engineers have always kept detailed notes.</p>
  253. <p>We can use GitHub issues as a really quick and easy way to do the same thing!</p>
  254. </div>
  255. </div>
  256. <div class="slide">
  257. <img alt="simonw/public-notes/issues" loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.026.jpeg">
  258. </div>
  259. <div class="slide">
  260. <img alt="Tell people what you did! (It's so easy to skip this step)" loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.027.jpeg"><div>
  261. <p>The last thing I want to encourage you to do is this: if you do project, tell people what it is you did!</p>
  262. <p>This counts for both personal and work projects. It’s so easy to skip this step.</p>
  263. <p>Once you’ve shipped a feature or built a project, it’s so tempting to skip the step of spending half an hour or more writing about the work you have done.</p>
  264. <p>But you are missing out on <em>so much</em> of the value of your work if you don’t give other people a chance to understand what you did.</p>
  265. <p>I wrote more about this here: <a href="https://simonwillison.net/2022/Nov/6/what-to-blog-about/">What to blog about</a>.</p>
  266. </div>
  267. </div>
  268. <div class="slide">
  269. <img alt="Release notes (with dates)" loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.028.jpeg"><div>
  270. <p>For projects with releases, release notes are a really good way to do this.</p>
  271. <p>I like using GitHub releases for this—they’re quick and easy to write, and I have automation setup for my projects such that creating release notes in GitHub triggers a build and release to PyPI.</p>
  272. <p>I’ve done over 1,000 releases in this way. Having them automated is crucial, and having automation makes it really easy to ship releases more often.</p>
  273. <p>Please make sure your release notes have dates on them. I need to know when your change went out, because if it’s only a week old it’s unlikely people will have upgraded to it yet, whereas a change from five years ago is probably safe to depend on.</p>
  274. <p>I wrote more about <a href="https://simonwillison.net/2022/Jan/31/release-notes/">writing better release notes</a> here.</p>
  275. </div>
  276. </div>
  277. <div class="slide">
  278. <img alt="Expand your definition of done to include writing about what you did" loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.029.jpeg"><div>
  279. <p>This is a mental trick which works really well for me. “No project of mine is finished until I’ve told people about it in some way” is a really useful habit to form.</p>
  280. </div>
  281. </div>
  282. <div class="slide">
  283. <img alt="Twitter threads (embed images + links + videos)" loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.030.jpeg"><div>
  284. <p>Twitter threads are (or were) a great low-effort way to write about a project. Build a quick thread with some links and images, and maybe even a video.</p>
  285. <p>Get a little unit about your project out into the world, and then you can stop thinking about it.</p>
  286. <p>(I’m trying to do this <a href="https://simonwillison.net/2022/Nov/5/mastodon/">on Mastodon now</a> instead.)</p>
  287. </div>
  288. </div>
  289. <div class="slide">
  290. <img alt="Get a blog" loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.031.jpeg"><div>
  291. <p>Even better: get a blog! Having your own corner of the internet to write about the work that you are doing is a small investment that will pay off many times over.</p>
  292. <p>(“Nobody blogs anymore” I said in the talk... Phil Gyford disagrees with that meme so much that he launched <a href="https://ooh.directory/blog/2022/welcome/">a new blog directory</a> to show how wrong it is.)</p>
  293. </div>
  294. </div>
  295. <div class="slide">
  296. <img alt="GUILT is the enemy of projects" loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.032.jpeg"><div>
  297. <p>The enemy of projects, especially personal projects, is <em>guilt</em>.</p>
  298. <p>The more projects you have, the more guilty you feel about working on any one of them—because you’re not working on the others, and those projects haven’t yet achieved their goals.</p>
  299. <p>You have to overcome guilt if you’re going to work on 185 projects at once!</p>
  300. </div>
  301. </div>
  302. <div class="slide">
  303. <img alt="Avoid side projects with user accounts. If i has user accounts it's not a side-project, it's an unpaid job." loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.033.jpeg"><div>
  304. <p>This is the most important tip: avoid side projects with user accounts.</p>
  305. <p>If you build something that people can sign into, that’s not a side-project, it’s an unpaid job. It’s a very big responsibility, avoid at all costs!</p>
  306. <p>Almost all of my projects right now are open source things that people can run on their own machines, because that’s about as far away from user accounts as I can get.</p>
  307. <p>I still have a responsibility for shipping security updates and things like that, but at least I’m not holding onto other people’s data for them.</p>
  308. </div>
  309. </div>
  310. <div class="slide">
  311. <img alt="If your project is tested and documented, you have nothing to feel guilty about. That's what I tell myself anyway!" loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.034.jpeg"><div>
  312. <p>I feel like if your project is tested and documented, <em>you have nothing to feel guilty about</em>.</p>
  313. <p>You have put a thing out into the world, and it has tests to show that it works, and it has documentation that explains what it is.</p>
  314. <p>This means I can step back and say that it’s OK for me to work on other things. That thing there is a unit that makes sense to people.</p>
  315. <p>That’s what I tell myself anyway! It’s OK to have 185 projects provided they all have documentation and they all have tests.</p>
  316. <p>Do that and the guilt just disappears. You can live guilt free!</p>
  317. </div>
  318. </div>
  319. <div class="slide">
  320. <img alt="Thank you - simonwillison.net - twitter.com/simonw / github.com/simonw" loading="lazy" src="https://static.simonwillison.net/static/2022/djangocon-productivity/productivity.035.jpeg"><div>
  321. <p>You can follow me on Mastodon at <a href="https://fedi.simonwillison.net/@simon">@simon@simonwillison.net</a> or on GitHub at <a href="https://github.com/simonw">github.com/simonw</a>. Or subscribe to my blog at <a href="https://simonwillison.net/">simonwillison.net</a>!</p>
  322. <p>From the Q&amp;A:</p>
  323. <ul>
  324. <li>You’ve tweeted about using GitHub Projects. Could you talk about that?
  325. <ul>
  326. <li>
  327. <a href="https://docs.github.com/en/issues/planning-and-tracking-with-projects/learning-about-projects/about-projects">GitHub Projects V2</a> is the perfect TODO list for me, because it lets me bring together issues from different repositories. I use a project called “Everything” on a daily basis (it’s my browser default window)—I add issues to it that I plan to work on, including personal TODO list items as well as issues from my various public and private repositories. It’s kind of like a cross between Trello and Airtable and I absolutely love it.</li>
  328. </ul>
  329. </li>
  330. <li>How did you move notes from the private to the public repo?
  331. <ul>
  332. <li>GitHub doesn’t let you do this. But there’s a trick I use involving a <code>temp</code> repo which I switch between public and private to help transfer notes. More in this TIL.</li>
  333. </ul>
  334. </li>
  335. <li>Question about the perfect commit: do you commit your failing tests?
  336. <ul>
  337. <li>I don’t: I try to keep the commits that land on my <code>main</code> branch always passing. I’ll sometimes write the failing test before the implementation and then commit them together. For larger projects I’ll work in a branch and then squash-merge the final result into a perfect commit to main later on.</li>
  338. </ul>
  339. </li>
  340. </ul>
  341. </div>
  342. </article>
  343. <hr>
  344. <footer>
  345. <p>
  346. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  347. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  348. </svg> Accueil</a> •
  349. <a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2">
  350. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-rss2"></use>
  351. </svg> Suivre</a> •
  352. <a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie">
  353. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-user-tie"></use>
  354. </svg> Pro</a> •
  355. <a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail">
  356. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-mail"></use>
  357. </svg> Email</a> •
  358. <abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2">
  359. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-hammer2"></use>
  360. </svg> Légal</abbr>
  361. </p>
  362. <template id="theme-selector">
  363. <form>
  364. <fieldset>
  365. <legend><svg class="icon icon-brightness-contrast">
  366. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-brightness-contrast"></use>
  367. </svg> Thème</legend>
  368. <label>
  369. <input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
  370. </label>
  371. <label>
  372. <input type="radio" value="dark" name="chosen-color-scheme"> Foncé
  373. </label>
  374. <label>
  375. <input type="radio" value="light" name="chosen-color-scheme"> Clair
  376. </label>
  377. </fieldset>
  378. </form>
  379. </template>
  380. </footer>
  381. <script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script>
  382. <script>
  383. function loadThemeForm(templateName) {
  384. const themeSelectorTemplate = document.querySelector(templateName)
  385. const form = themeSelectorTemplate.content.firstElementChild
  386. themeSelectorTemplate.replaceWith(form)
  387. form.addEventListener('change', (e) => {
  388. const chosenColorScheme = e.target.value
  389. localStorage.setItem('theme', chosenColorScheme)
  390. toggleTheme(chosenColorScheme)
  391. })
  392. const selectedTheme = localStorage.getItem('theme')
  393. if (selectedTheme && selectedTheme !== 'undefined') {
  394. form.querySelector(`[value="${selectedTheme}"]`).checked = true
  395. }
  396. }
  397. const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
  398. window.addEventListener('load', () => {
  399. let hasDarkRules = false
  400. for (const styleSheet of Array.from(document.styleSheets)) {
  401. let mediaRules = []
  402. for (const cssRule of styleSheet.cssRules) {
  403. if (cssRule.type !== CSSRule.MEDIA_RULE) {
  404. continue
  405. }
  406. // WARNING: Safari does not have/supports `conditionText`.
  407. if (cssRule.conditionText) {
  408. if (cssRule.conditionText !== prefersColorSchemeDark) {
  409. continue
  410. }
  411. } else {
  412. if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
  413. continue
  414. }
  415. }
  416. mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
  417. }
  418. // WARNING: do not try to insert a Rule to a styleSheet you are
  419. // currently iterating on, otherwise the browser will be stuck
  420. // in a infinite loop…
  421. for (const mediaRule of mediaRules) {
  422. styleSheet.insertRule(mediaRule.cssText)
  423. hasDarkRules = true
  424. }
  425. }
  426. if (hasDarkRules) {
  427. loadThemeForm('#theme-selector')
  428. }
  429. })
  430. </script>
  431. </body>
  432. </html>