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.

1 年之前
12345
  1. title: Clever Code Considered Harmful
  2. url: https://www.joshwcomeau.com/career/clever-code-considered-harmful/
  3. hash_url: a09b5bf450d2cf86fb9e9d6f13b070e0
  4. <p class="sc-e646da6e-0 buyZsM">There is something undeniably satisfying about coming up with clever solutions to hard problems. There is a joy when you challenge yourself to use recursion instead of iteration, for example, or when you create elegant, cascading layers of abstraction that ensure code is never duplicated.</p><p class="sc-e646da6e-0 buyZsM">My favourite outlet for this kind of programming is <a href="https://projecteuler.net" rel="noopener noreferrer" target="_blank" class="sc-7af019d9-0 fZHCDu sc-53808c2b-0 NuBMO">Project Euler</a>.</p><p class="sc-e646da6e-0 buyZsM">Project Euler is a repository of challenges based around advanced mathematics, meant to be solved with software. The catch is that your program should run in under a minute, on 2004-era hardware. That means that a brute-force solution often won’t cut it, and you’ll have to come up with a smarter solution.</p><p class="sc-e646da6e-0 buyZsM">Here’s an example:</p><p class="sc-e646da6e-0 buyZsM"><span type="default" class="sc-9a7b0d0-0 jvgAVO"><img src="/images/legacy/1__sbbO5gKvxk__TuJOIRMUzKA.png" alt="A math problem, to calculate numbers arranged in a spiral problem. Screenshot of this page: https://projecteuler.net/problem=58" class="sc-9a7b0d0-2 jkoJNm"><span class="sc-9a7b0d0-1 fbsvTj">Helpful tip: Euler is pronounced “Oiler”. Impress your math friends with this knowledge!</span></span></p><p class="sc-e646da6e-0 buyZsM">Once you’ve solved a problem, you’re able to view solutions that other people have shared. And oh wow, do people come up with terse, clever solutions for these things.</p><aside class="sc-3583c7cd-0 sc-3583c7cd-3 Nmjxf bsUafj"><p class="sc-3583c7cd-6 iESgn">I’m kinda breaking the Project Euler rules here— you’re not supposed to share solutions, to avoid spoiling it for others— but don’t worry, these solutions are totally indecipherable. You won’t spoil the challenge.</p></aside><p class="sc-e646da6e-0 buyZsM">Here's one by a user named WillNess written in Haskell, a functional programming language:</p><pre></pre><p class="sc-e646da6e-0 buyZsM">Here's another one, written in <a href="https://en.wikipedia.org/wiki/J_%28programming_language%29" rel="noopener noreferrer" target="_blank" class="sc-7af019d9-0 fZHCDu sc-53808c2b-0 NuBMO">J lang</a> by user u56:</p><pre></pre><p class="sc-e646da6e-0 buyZsM">Clearly, it takes a tremendous amount of skill to solve such a hard problem with such a small amount of code. I would feel very pleased with myself if I was able to write a solution like this; my solutions are always way longer and less elegant.</p><p class="sc-e646da6e-0 buyZsM">This is not "production-ready" code, though. This is recreational code. This is code that you write to feel clever, to impress fellow math nerds, to exercise your brain. When you solve a Project Euler problem, you never have to look at that code again. It's disposable. We aren't handing it off to someone else to maintain it.</p><p class="sc-e646da6e-0 buyZsM">This is a different universe from the environments we write code in day-to-day.</p><p class="sc-e646da6e-0 buyZsM">When it comes to day-to-day production code, here's the barometer I like to use: will a junior developer, someone at the very start of their career, struggle to understand this code?</p><p class="sc-e646da6e-0 buyZsM">In the context of a shared codebase, good code is simple code. Code that doesn’t do anything fancy. Code that makes minimal use of abstractions. Code that you’d use to explain fundamental concepts to novices.</p><p class="sc-e646da6e-0 buyZsM">A few years ago, I was asked to review a pull request that included the following function:</p><pre></pre><p class="sc-e646da6e-0 buyZsM">I suggested we re-write it like this:</p><pre></pre><p class="sc-e646da6e-0 buyZsM">The original version has lots of things going for it, on paper:</p><ul class="sc-19724b12-1 hKaNYm"><li class="sc-19724b12-3 kuSaGk"><span class="sc-19724b12-5 jxFLLh"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg></span><p class="sc-19724b12-4 kJtFVp">Less code (only 4 statements instead of 7)</p></li><li class="sc-19724b12-3 kuSaGk"><span class="sc-19724b12-5 jxFLLh"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg></span><p class="sc-19724b12-4 kJtFVp">No duplication (the second version uses two <code class="sc-1a723291-0 YrISa">if</code> statements that do essentially the same thing)</p></li><li class="sc-19724b12-3 kuSaGk"><span class="sc-19724b12-5 jxFLLh"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg></span><p class="sc-19724b12-4 kJtFVp">Doesn't mutate any variables</p></li><li class="sc-19724b12-3 kuSaGk"><span class="sc-19724b12-5 jxFLLh"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg></span><p class="sc-19724b12-4 kJtFVp">More scalable: could easily be rewritten to handle 10 fields instead of 2.</p></li></ul><p class="sc-e646da6e-0 buyZsM">And yet, the original version is <em class="sc-51e913c7-0 cWQfrg">way</em> harder to understand. I had to burn a ton of calories trying to work out what it was doing. I suspect many junior developers would be completely stumped trying to decipher it.</p><p class="sc-e646da6e-0 buyZsM">In my opinion, the readability cost of the functional version is too high. It's not worth it.</p><p class="sc-e646da6e-0 buyZsM">To understand why I think readability is such a crucial attribute of good code, let’s look at a popular open-source library, <a href="https://github.com/lodash/lodash" rel="noopener noreferrer" target="_blank" class="sc-7af019d9-0 fZHCDu sc-53808c2b-0 NuBMO">lodash</a>.</p><p class="sc-e646da6e-0 buyZsM"><span type="default" class="sc-9a7b0d0-0 jvgAVO"><img src="/images/legacy/1__cDMSqwCi1aSPRxXdG5PUzg.png" alt="Screenshot of the “lodash” Github page" class="sc-9a7b0d0-2 jkoJNm"></span></p><p class="sc-e646da6e-0 buyZsM">lodash is an immensely popular tool. It’s downloaded more than 26,000,000 times a week on NPM alone, and has over 42,000 Github stars. There is something absolutely curious about it, though; it consistently has less than 10 open issues.</p><p class="sc-e646da6e-0 buyZsM">"Inbox zero" is a thing with email, but it <em class="sc-51e913c7-0 cWQfrg">never</em> happens with issues on popular projects. And yet, lodash often sits at zero open issues. It's been like this for years.</p><p class="sc-e646da6e-0 buyZsM">Well, one reason is that the library’s primary author, John-David Dalton, is a passionate maintainer who spends a lot of his time triaging issues as they come in. But I don’t believe anyone, no matter how superhuman, can get a library this popular to 0 issues alone.</p><p class="sc-e646da6e-0 buyZsM">Years ago, I heard JDD on <a href="https://podcasts.apple.com/us/podcast/012-jsair-lodash-open-source-with-john-david-dalton/id1066446588?i=1000364088851" rel="noopener noreferrer" target="_blank" class="sc-7af019d9-0 fZHCDu sc-53808c2b-0 NuBMO">a podcast</a> talk about how the key to managing a project like this is to encourage lots of folks to contribute to it. One of the ways he’s done this is by keeping the code at a pretty fundamental level; by using simple, basic constructs, it ensures that aspiring contributors can understand and contribute to the code, regardless of how much experience they have. I believe JDD mentions that they prefer if/else to ternaries simply because less people have experience with ternaries. When the goal is to keep the code simple, the expressive power of the ternary operator is detrimental.</p><p class="sc-e646da6e-0 buyZsM">This is important to keep in mind if you’re building an open-source tool, but it’s <em class="sc-51e913c7-0 cWQfrg">even more important</em> if you’re working in a production codebase with other humans. Especially ones that have less experience than you.</p><aside class="sc-3583c7cd-0 sc-3583c7cd-1 Nmjxf bvghBJ"><p class="sc-3583c7cd-5 crvSnl"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg></p><strong class="sc-3583c7cd-4 ghVlSM">Zero issues no more</strong><div class="sc-3583c7cd-6 iESgn"><p class="sc-e646da6e-0 buyZsM">As I write this update in 2023, Lodash has a couple hundred open issues. This is likely because its maintainer, JDD, has moved onto other projects.</p></div></aside><p class="sc-e646da6e-0 buyZsM">Have you ever heard someone say this?</p><blockquote class="sc-4c838171-0 lkTYca"><p class="sc-e646da6e-0 buyZsM">Less code means less space for bugs to hide</p></blockquote><p class="sc-e646da6e-0 buyZsM">This argument makes the case that short code is better, because it'll be easier to spot bugs. With every additional character you type, you're increasing the likelihood of a mistake.</p><p class="sc-e646da6e-0 buyZsM">This is true when it comes to <em>typos</em>, sure. But typos tend to be easy to catch and fix. The <em class="sc-51e913c7-0 cWQfrg">really</em> troublesome bugs — the ones that tend to break user experiences for <em>weeks</em> as developers pass the support ticket around like a hot potato — are often caused by too much <em class="sc-51e913c7-0 cWQfrg">complexity</em>, not too many characters.</p><p class="sc-e646da6e-0 buyZsM">In order to debug an issue, you have to wrap your mind around what the code is doing. When you create an abstraction to reduce duplication (“Make it DRY”), you add a layer of indirection that your mind has to unpack. The harder it is for your mental model to account for every edge-case and possible state, the more likely it is that you’ll have trouble diagnosing what’s gone wrong.</p><blockquote class="sc-4c838171-0 lkTYca"><p class="sc-e646da6e-0 buyZsM">“Everyone knows that debugging is twice as hard as writing a program in the first place. So if you’re as clever as you can be when you write it, how will you ever debug it?”</p></blockquote><p class="sc-e646da6e-0 buyZsM">— <a href="https://en.wikipedia.org/wiki/Brian_Kernighan" rel="noopener noreferrer" target="_blank" class="sc-7af019d9-0 fZHCDu sc-53808c2b-0 NuBMO">Brian Kernighan</a>, The Elements of Programming Style</p><p class="sc-e646da6e-0 buyZsM">If the goal is to reduce complexity, and abstractions add complexity, should we abolish abstractions altogether?</p><p class="sc-e646da6e-0 buyZsM">Well, no. Abstractions are everywhere. Loops are abstractions. Functions are abstractions. Programming languages themselves are abstractions over machine code, which itself is an abstraction over transistors flickering off and on really fast. It’s abstractions all the way down.</p><p class="sc-e646da6e-0 buyZsM">The key is to weigh the cost of an abstraction against its benefit. Say we’re building a React app, and we have a list of 100 things to render. We could copy/paste the same JSX 100 times, or we could map over an array and write the JSX once. The “complex” solution in this case is totally worth it, because the underlying complexity is commonly known, and the alternative would be burdensome to maintain.</p><p class="sc-e646da6e-0 buyZsM">As we build stuff, we make trade-off decisions like this all the time. If I have a point, it’s that we should consider these tradeoffs with our most junior teammates in mind; how much complexity are we adding for them? Is it worth it?</p><p class="sc-e646da6e-0 buyZsM">Code sometimes has to be complex, because the real world is complex and our software has to model it! We won’t always be able to write code that a junior engineer can easily parse and contribute to. Sometimes the business logic is genuinely really tricky, sometimes we have to use an API with an inscrutable interface, and so on.</p><p class="sc-e646da6e-0 buyZsM">I think the best way to deal with this is to try and sequester complexity. Set up clear boundaries between the simple stuff and the complex stuff. Don’t let the complexity seep into the surrounding areas.</p><p class="sc-e646da6e-0 buyZsM">John-David Dalton did this with lodash. According to his interview in <a href="https://podcasts.apple.com/us/podcast/012-jsair-lodash-open-source-with-john-david-dalton/id1066446588?i=1000364088851" rel="noopener noreferrer" target="_blank" class="sc-7af019d9-0 fZHCDu sc-53808c2b-0 NuBMO">that same podcast</a>, the vast majority of lodash code is simple and easy-to-follow, and they’ve pushed the complex bits to a sophisticated core that handles the hard problems. This means that most contributors are spared from having to deal with that complexity, since it isn’t sprinkled across the application.</p><p class="sc-e646da6e-0 buyZsM">If your app is architected so that the most complex concerns are all dealt with in the same place, you can keep the overwhelming majority of your app’s surface area simple.</p><p class="sc-e646da6e-0 buyZsM">What if the junior engineer needs to work on that complex core? Well, good news! They have a more-senior person (you) to help guide them through it. Mentorship and education is a huge part of being a senior developer.</p><p class="sc-e646da6e-0 buyZsM">One of my favourite talks from React Rally last year was <a href="https://www.youtube.com/watch?v=-NP_upexPFg" rel="noopener noreferrer" target="_blank" class="sc-7af019d9-0 fZHCDu sc-53808c2b-0 NuBMO">Chantastic’s “Hot Garbage; Clean Code is Dead”</a>. I won’t spoil the talk (seriously, go watch it!), but one of the takeaways I took from it is that everyone suffers from impostor syndrome, and as a result, we're always trying to prove to each other that we know our stuff. If we write a function that is super clever and indecipherable, our co-workers will know that we're smart, that we belong here!</p><p class="sc-e646da6e-0 buyZsM">What I've come to realize, though, is that anyone can write code that seems complicated. The hard thing is solving complex problems with simple code. If you can develop that skill, nobody will ever doubt your abilities. ✨</p>