<?xml version="1.0" encoding="utf-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>sglavoie.com – Tag: Skimming Notes</title><link>https://www.sglavoie.com/tags/skimming-notes/</link><description>Posts and writings by Sébastien Lavoie. Customization setups and articles on productivity for developers who like minimalism.</description><language>en</language><atom:link href="https://www.sglavoie.com/tags/skimming-notes/feed.xml" rel="self" type="application/rss+xml"/><lastBuildDate>Sun, 30 Mar 2025 11:18:00 -0600</lastBuildDate><item><title>Reading notes: A Philosophy of Software Design, 2nd Edition</title><link>https://www.sglavoie.com/posts/book-summary-philosophy-software-design-2nd-edition/</link><description>A Philosophy of Software Design, 2nd Edition by John Osterhout beautifully covers the nature of complexity in software. It provides clear strategies to keep complexity as low as possible while giving real-world examples along the way from his own work (e.g., RAMCloud or the Tcl programming language). He also gathered, for instance, interesting sources of various designs for a GUI text editor from his students, highlighting essential points to keep in mind while designing software.</description><content:encoded>&lt;hr&gt;
&lt;h2 id="nature-of-complexity"&gt;Nature of complexity&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Change amplification: The first symptom of complexity is that a seemingly simple change requires code modifications in many different places.&amp;rdquo;&lt;/em&gt; (e.g., refactoring a project to inject dependencies in tests, because the system was hard to test in the first place)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Cognitive load: The second symptom of complexity is cognitive load, which refers to how much a developer needs to know in order to complete a task.&amp;rdquo;&lt;/em&gt; (e.g., close a file where it is opened whenever possible, instead of leaking context and pushing the responsibility onto the caller)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Unknown unknowns: The third symptom of complexity is that it is not obvious which pieces of code must be modified to complete a task, or what information a developer must have to carry out the task successfully.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;One of the most important goals of good design is for a system to be obvious.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="strategic-vs-tactical-programming"&gt;Strategic vs. tactical programming&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;it&amp;rsquo;s worth taking a little extra time to find a simple design for each new class; rather than implementing the first idea that comes to mind, try a couple of alternative designs and pick the cleanest one. Try to imagine a few ways in which the system might need to be changed in the future and make sure that will be easy with your design&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="modules-should-be-deep"&gt;Modules should be deep&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Modular design
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;In general, if a developer needs to know a particular piece of information in order to use a module, then that information is part of the module&amp;rsquo;s interface.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Abstractions
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;The key to designing abstractions is to understand what is important, and to look for designs that minimize the amount of information that is important&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;A deep module is one in which the interface is small and the implementation is large.
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Deep modules such as Unix I/O and garbage collectors provide powerful abstractions because they are easy to use, yet they hide significant implementation complexity.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Shallow modules
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;A shallow module is one whose interface is complicated relative to the functionality it provides.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="information-hiding"&gt;Information hiding&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;If a piece of information is hidden, there are no dependencies on that information outside the module containing the information, so a design change related to that information will affect only the one module&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Information leakage
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;If the affected classes are relatively small and closely tied to the leaked information, it may make sense to merge them into a single class.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;When designing modules, focus on the knowledge that&amp;rsquo;s needed to perform each task, not the order in which tasks occur.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;it&amp;rsquo;s important to avoid exposing internal data structures as much as possible&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;The best features are the ones you get without even knowing they exist.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;As a software designer, your goal should be to minimize the amount of information needed outside a module; for example, if a module can automatically adjust its configuration, that is better than exposing configuration parameters&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;think about the different pieces of knowledge that are needed to carry out the tasks of your application, and design each module to encapsulate one or a few of those pieces of knowledge. This will produce a clean and simple design with deep modules.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="general-purpose-modules-are-deeper"&gt;General-Purpose Modules are Deeper&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;specialization leads to complexity&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Generality leads to better information hiding&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Eliminate special cases in code&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="different-layer-different-abstraction"&gt;Different Layer, Different Abstraction&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;A pass-through method is one that does nothing except pass its arguments to another method, usually with the same API as the pass-through method. This typically indicates that there is not a clean division of responsibility between the classes.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="pull-complexity-downwards"&gt;Pull Complexity Downwards&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Figure out complexity in the library instead of pushing it up to the user.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;quot;[&amp;hellip;] you should avoid configuration parameters as much as possible. Before exporting a configuration parameter, ask yourself: will users (or higher-level modules) be able to determine a better value than we can determine here?&amp;quot;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="better-together-or-better-apart"&gt;Better Together Or Better Apart?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Bring together if information is shared&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Bring together if it will simplify the interface&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Bring together to eliminate duplication&amp;rdquo;&lt;/em&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;goto&lt;/code&gt; to exit nested code blocks early as long as it&amp;rsquo;s not too complex.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;On Splitting long methods: &lt;em&gt;&amp;ldquo;If you make a split of this form and then find yourself flipping back and forth between the parent and child to understand how they work together, that is a red flag (&amp;ldquo;Conjoined Methods&amp;rdquo;) indicating that the split was probably a bad idea.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;If the caller has to invoke each of the separate methods, passing state back and forth between them, then splitting is not a good idea.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="define-errors-out-of-existence"&gt;Define Errors Out Of Existence&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;The key overall lesson from this chapter is to &lt;strong&gt;reduce the number of places where exceptions must be handled&lt;/strong&gt;; in some cases the semantics of operations can be modified so that the normal behavior handles all situations and there is no exceptional condition to report [&amp;hellip;].&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;The best way to eliminate exception handling complexity is to &lt;strong&gt;define your APIs so that there are no exceptions to handle&lt;/strong&gt;: define errors out of existence&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;E.g., the Java &lt;code&gt;substring&lt;/code&gt; method could remove its &lt;code&gt;IndexOutOfBoundsException&lt;/code&gt; by automatically rounding the index to the nearest valid value.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;quot;&lt;strong&gt;Exception masking&lt;/strong&gt; doesn&amp;rsquo;t work in all situations, but it is a powerful tool in the situations where it works. It results in deeper classes, since it reduces the class&amp;rsquo;s interface (fewer exceptions for users to be aware of) and adds functionality in the form of the code that masks the exception. Exception masking is an example of pulling complexity downward.&amp;quot;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;The idea behind exception aggregation is to handle many exceptions with a single piece of code; rather than writing distinct handlers for many individual exceptions, handle them all in one place with a single handler.&amp;rdquo;&lt;/em&gt; (e.g., a web server dispatching different types of requests and catching all sorts of errors).&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;In most applications there will be certain errors that are not worth trying to handle. Typically, these errors are difficult or impossible to handle and don&amp;rsquo;t occur very often. The simplest thing to do in response to these errors is to print diagnostic information and then abort the application.&amp;rdquo;&lt;/em&gt; (e.g., out-of-memory errors).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="design-it-twice"&gt;Design it Twice&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;The initial design experiments will probably result in a significantly better design, which will more than pay for the time spent designing it twice&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;The process of devising and comparing multiple approaches will teach you about the factors that make designs better or worse. Over time, this will make it easier for you to rule out bad designs and hone in on really great ones.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="why-write-comments-the-four-excuses"&gt;Why Write Comments? The Four Excuses&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Good code is self-documenting.&lt;/em&gt; Comments serve as precisions on abstractions.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;I don&amp;rsquo;t have time to write comments.&lt;/em&gt; The benefits of having documentation quickly offset the cost of writing it.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Comments get out of date and become misleading.&lt;/em&gt; Keep the documentation close to where it needs to be, so you update it.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;All the comments I have seen are worthless.&lt;/em&gt; Be the change you want to see.&lt;/li&gt;
&lt;li&gt;Benefits of well-written comments
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;The overall idea behind comments is to capture information that was in the mind of the designer but couldn&amp;rsquo;t be represented in the code.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;One of the purposes of comments is to make it it unnecessary to read the code.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="comments-should-describe-things-that-arent-obvious-from-the-code"&gt;Comments Should Describe Things that Aren&amp;rsquo;t Obvious from the Code&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Don&amp;rsquo;t repeat the code&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Lower-level comments add precision&amp;rdquo;&lt;/em&gt;
&lt;ul&gt;
&lt;li&gt;Focus on what the variable represents, not how it is modified (the latter is explained by the code).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Higher-level comments enhance intuition&amp;rdquo;&lt;/em&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;What is the simplest thing you can say that explains everything in the code? What is the most important thing about this code?&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Interface documentation&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Implementation comments: what and why, not how&amp;rdquo;&lt;/em&gt;
&lt;ul&gt;
&lt;li&gt;Describe at a more abstract level to help understand what is happening and why. The code tells the how.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Cross-module design decisions
&lt;ul&gt;
&lt;li&gt;One possible solution is to use a &lt;code&gt;designNotes.md&lt;/code&gt; file or similar to jot down all major decisions, and point to it from the code (e.g., &lt;em&gt;See &lt;code&gt;topic&lt;/code&gt; in designNotes&lt;/em&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="choosing-names"&gt;Choosing names&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Great names are &lt;em&gt;&amp;ldquo;precise, unambiguous, and intuitive.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Use names consistently: make it so that a concept is expressed in the same words regardless of the context in which it is found if it makes sense to do so.&lt;/li&gt;
&lt;li&gt;Avoid extra words: no more Hungarian Notation with modern IDEs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="use-comments-as-part-of-the-design-process"&gt;Use comments as part of the design process&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Delayed comments are bad comments
&lt;ul&gt;
&lt;li&gt;You won&amp;rsquo;t remember exactly what you had in mind and why you did things if you delay writing documentation: design decisions will get lost.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Write the comments first
&lt;ul&gt;
&lt;li&gt;No backlog of unwritten comments.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;it produces better comments.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Comments are a design tool
&lt;ul&gt;
&lt;li&gt;Writing them at the beginning &lt;em&gt;&amp;ldquo;improves the system design.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;If a method or variable requires a long comment, it is a red flag that you don&amp;rsquo;t have a good abstraction.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Early comments are fun comments
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;If you are programming strategically, where your main goal is a great design rather than just writing code that works, then writing comments should be fun, since that&amp;rsquo;s how you identify the best designs.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;I&amp;rsquo;m looking for the design that can be expressed completely and clearly in the fewest words. The simpler the comments, the better I feel about my design.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Are early comments expensive?
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Writing the comments first will mean that the abstractions will be more stable before you start writing code.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;In contrast, if you write the code first, the abstractions will probably evolve as you code, which will require more code revisions than the comments-first approach.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="modifying-existing-code"&gt;Modifying existing code&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Whenever you modify any code, try to find a way to improve the system design at least a little bit in the process. If you&amp;rsquo;re not making the design better, you are probably making it worse.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Maintaining comments: keep the comments near the code
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;The farther a comment is from its associated code, the less likely it is that it will be updated properly.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;In general, the farther a comment is from the code it describes, the more abstract it should be (this reduces the likelihood that the comment will be invalidated by code changes).&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Comments belong in the code, not the commit log
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;An example is a commit message describing a subtle problem that motivated a code change. If this isn&amp;rsquo;t documented in the code, then a developer might come along later and undo the change without realizing that they have re-created a bug.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Maintaining comments: avoid duplication&lt;/li&gt;
&lt;li&gt;Maintaining comments: check the diffs
&lt;ul&gt;
&lt;li&gt;Review changes during the pre-commit phase to make sure documentation stays up-to-date with the code changes.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Higher-level comments are easier to maintain
&lt;ul&gt;
&lt;li&gt;Most helpful comments do not repeat the code, they provide an abstraction that can&amp;rsquo;t be expressed easily by the code alone.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="consistency"&gt;Consistency&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Similar things are done in a similar way.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Consistency creates cognitive leverage: once you have learned how something is done in one place, you can use that knowledge to immediately understand other places that use the same approach.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Ensuring consistency
&lt;ul&gt;
&lt;li&gt;Document most important conventions.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;the value of consistency over inconsistency is almost always greater than the value of one approach over another.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="code-should-be-obvious"&gt;Code should be obvious&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;the best way to determine the obviousness of code is through code reviews&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;If someone reading your code says it&amp;rsquo;s not obvious, then it&amp;rsquo;s not obvious, no matter how clear it may seem to you&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Things that make code more obvious
&lt;ul&gt;
&lt;li&gt;Good names&lt;/li&gt;
&lt;li&gt;Consistency&lt;/li&gt;
&lt;li&gt;Judicious use of white space&lt;/li&gt;
&lt;li&gt;Blank lines&lt;/li&gt;
&lt;li&gt;Comments&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Things that make code less obvious
&lt;ul&gt;
&lt;li&gt;Event-driven programming&lt;/li&gt;
&lt;li&gt;Generic containers&lt;/li&gt;
&lt;li&gt;Different types for declaration and allocation&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="software-trends"&gt;Software trends&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Object-oriented programming and inheritance
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;In order for an interface to have many implementations, it must capture the essential features of all the underlying implementations while steering clear of the details that differ between the implementations; this notion is at the heart of abstraction.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Class hierarchies that use implementation inheritance extensively tend to have high complexity.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Before using implementation inheritance, consider whether an approach based on composition can provide the same benefits.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Agile development
&lt;ul&gt;
&lt;li&gt;May encourage a more tactical approach to programming. Balance is required.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Test-driven development
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;it focuses attention on getting specific features working, rather than finding the best design&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;One place where it makes sense to write the tests first is when fixing bugs. Before fixing a bug, write a unit test that fails because of the bug.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Design patterns
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;As with many ideas in software design, the notion that design patterns are good doesn’t necessarily mean that more design patterns are better.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Getters and setters
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;It’s better to avoid getters and setters (or any exposure of implementation data) as much as possible.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="designing-for-performance"&gt;Designing for performance&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;How to think about performance
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;The key is to develop an awareness of which operations are fundamentally expensive.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Measure before (and after) modifying
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;the goal is to identify a small number of very specific places where the system is currently spending a lot of time, and where you have ideas for improvement.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;If the changes didn’t make a measurable difference in performance, then back them out (unless they made the system simpler).&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Design around the critical path
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;When redesigning for performance, try to minimize the number of special cases you must check.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Complicated code tends to be slow because it does extraneous or redundant work.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="decide-what-matters"&gt;Decide what matters&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Minimize what matters&lt;/li&gt;
&lt;li&gt;How to emphasize things that matter
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;important things should appear in places where they are more likely to be seen, such as interface documentation, names, or parameters to heavily used methods.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;repetition: key ideas appear over and over again.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;The things that matter the most should be at the heart of the system, where they determine the structure of things around them.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Mistakes
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;treat too many things as important. When this happens, unimportant things clutter up the design, adding complexity and increasing cognitive load.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;fail to recognize that something is important. This mistake leads to situations where important information is hidden, or important functionality is not available so developers must continually recreate it.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Thinking more broadly
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;the best way to make a document easy to read is to identify a few key concepts at the beginning and structure the remainder of the document around them. When discussing the details of a system, it helps to tie them back to the overall concepts.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Focusing on what is important is also a great life philosophy: identify a few things that matter most to you, and try to spend as much of your energy as possible on those things.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;The reward for being a good designer is that you get to spend a larger fraction of your time in the design phase, which is fun. Poor designers spend most of their time chasing bugs in complicated and brittle code.&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Sébastien Lavoie</dc:creator><pubDate>Sun, 30 Mar 2025 11:18:00 -0600</pubDate><guid isPermaLink="false">https://www.sglavoie.com/posts/book-summary-philosophy-software-design-2nd-edition/</guid><category>learnings</category><category>books</category><category>design</category><category>software</category><category>skimming-notes</category></item><item><title>Book summary: 100 Go Mistakes and How to Avoid Them</title><link>https://www.sglavoie.com/posts/book-summary-100-go-mistakes-and-how-to-avoid-them/</link><description>100 Go Mistakes and How to Avoid Them is a truly insightful book by Teiva Harsanyi that covers common mistakes made by Go developers. This summary provides a high-level overview of the book’s content.</description><content:encoded>&lt;hr&gt;
&lt;h2 id="go-simple-to-learn-but-hard-to-master"&gt;Go: Simple to learn but hard to master&lt;/h2&gt;
&lt;h3 id="go-outline"&gt;Go outline&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Go does not have:
&lt;ul&gt;
&lt;li&gt;Type inheritance;&lt;/li&gt;
&lt;li&gt;Macros;&lt;/li&gt;
&lt;li&gt;Partial functions;&lt;/li&gt;
&lt;li&gt;Support for lazy variable evaluation;&lt;/li&gt;
&lt;li&gt;Support for immutability;&lt;/li&gt;
&lt;li&gt;Operator overloading;&lt;/li&gt;
&lt;li&gt;Pattern matching;&lt;/li&gt;
&lt;li&gt;&amp;hellip;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Go is:
&lt;ul&gt;
&lt;li&gt;Stable;&lt;/li&gt;
&lt;li&gt;Expressive;&lt;/li&gt;
&lt;li&gt;Fast to compile;&lt;/li&gt;
&lt;li&gt;Safe (type-safety);&lt;/li&gt;
&lt;li&gt;Simple;&lt;/li&gt;
&lt;li&gt;Excellent for concurrent programming (goroutines, channels).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="simple-doesnt-mean-easy"&gt;Simple doesn&amp;rsquo;t mean easy&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Concurrency in Go may be &lt;em&gt;simpler&lt;/em&gt; than in other languages, but it is still hard to get right, as per &lt;a href="https://engineering.purdue.edu/WukLab/GoStudy-ASPLOS19.pdf"&gt;this study&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="100-go-mistakes"&gt;100 Go mistakes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Learning from mistakes is efficient.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;the best time for brain growth is when we&amp;rsquo;re facing mistakes&amp;rdquo;&lt;/em&gt; (&lt;a href="https://pubmed.ncbi.nlm.nih.gov/22042726/"&gt;source&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;we can remember not only the error but also the context surrounding the mistake&amp;rdquo;&lt;/em&gt; (&lt;a href="https://pubmed.ncbi.nlm.nih.gov/27648988/"&gt;source&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Categories of mistakes
&lt;ul&gt;
&lt;li&gt;Bugs
&lt;ul&gt;
&lt;li&gt;Bugs are very costly (&lt;a href="https://news.synopsys.com/2021-01-06-Synopsys-Sponsored-CISQ-Research-Estimates-Cost-of-Poor-Software-Quality-in-the-US-2-08-Trillion-in-2020"&gt;source&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Bugs can be very dangerous (e.g., in the case of a radiation therapy machine).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Needless complexity
&lt;ul&gt;
&lt;li&gt;Don&amp;rsquo;t over-complicate things.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Weaker readability
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;the ratio of time spent reading versus writing is well over 10 to 1&amp;rdquo;&lt;/em&gt; (&lt;a href="https://www.oreilly.com/library/view/clean-code-a/9780136083238/"&gt;source&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Nested code.&lt;/li&gt;
&lt;li&gt;Data type representation.&lt;/li&gt;
&lt;li&gt;Not using named result parameters.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Suboptimal or unidiomatic organization
&lt;ul&gt;
&lt;li&gt;Utility packages.&lt;/li&gt;
&lt;li&gt;Init functions.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Lack of API convenience
&lt;ul&gt;
&lt;li&gt;Overusing &lt;code&gt;any&lt;/code&gt; types.&lt;/li&gt;
&lt;li&gt;Wrong creational pattern.&lt;/li&gt;
&lt;li&gt;Blindly applying OOP principles.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Under-optimized code
&lt;ul&gt;
&lt;li&gt;Lack of fundamental knowledge.&lt;/li&gt;
&lt;li&gt;Impacted performance.&lt;/li&gt;
&lt;li&gt;Floating-point accuracy.&lt;/li&gt;
&lt;li&gt;Poorly parallelized execution.&lt;/li&gt;
&lt;li&gt;Not knowing how to reduce allocations.&lt;/li&gt;
&lt;li&gt;Data alignment.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Lack of productivity
&lt;ul&gt;
&lt;li&gt;Know how to write efficient tests.&lt;/li&gt;
&lt;li&gt;Use the standard library.&lt;/li&gt;
&lt;li&gt;Use profilers and linters.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="code-and-project-organization"&gt;Code and project organization&lt;/h2&gt;
&lt;h3 id="1---unintended-variable-shadowing"&gt;1 - Unintended variable shadowing&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A variable name declared in a block can be re-declared in a nested block (&lt;em&gt;variable shadowing&lt;/em&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;go&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// client is shadowed&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// will always be nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tracing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;createClientWithTracing&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;createDefaultClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Solutions
&lt;ul&gt;
&lt;li&gt;Use a temporary variable name in the inner scope.&lt;/li&gt;
&lt;li&gt;Use the &lt;code&gt;=&lt;/code&gt; operator instead of &lt;code&gt;:=&lt;/code&gt; in the inner scope. Leads to a single assignment. Error handling can be handled in the outer scope (&lt;code&gt;client, err = ...&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Be caution with reusing &lt;code&gt;err&lt;/code&gt; as well.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2---unnecessary-nested-code"&gt;2 - Unnecessary nested code&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Put the happy path first and return early on all edge cases and errors.&lt;/li&gt;
&lt;li&gt;Instead of using &lt;code&gt;else&lt;/code&gt; blocks, use early returns.&lt;/li&gt;
&lt;li&gt;Instead of checking for an happy path, check for the error path and return where possible.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="3---misusing-init-functions"&gt;3 - Misusing init functions&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Refresher on &lt;code&gt;init&lt;/code&gt; functions
&lt;ul&gt;
&lt;li&gt;An init function is a special function that is executed before the main function. It takes no arguments and returns no values.&lt;/li&gt;
&lt;li&gt;Variables are initialized, the &lt;code&gt;init&lt;/code&gt; function is called, and then the &lt;code&gt;main&lt;/code&gt; function is called.&lt;/li&gt;
&lt;li&gt;Imported packages are initialized before the &lt;code&gt;main&lt;/code&gt; function is called and before the &lt;code&gt;init&lt;/code&gt; function in the main package is called because the main package depends on the imported packages.&lt;/li&gt;
&lt;li&gt;Packages can define multiple &lt;code&gt;init&lt;/code&gt; functions, in which case they are executed in alphabetical order based on the source code file name. Hence, we should not depend on the order of execution of &lt;code&gt;init&lt;/code&gt; functions as file names can change.&lt;/li&gt;
&lt;li&gt;The same source file can define multiple &lt;code&gt;init&lt;/code&gt; functions, in which case they are executed in the order they are defined in the file.&lt;/li&gt;
&lt;li&gt;The underscore operator (&lt;code&gt;_&lt;/code&gt;) can be used to import a package without using it. This is useful to execute the &lt;code&gt;init&lt;/code&gt; function of a package without using the package itself.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;init&lt;/code&gt; functions cannot be invoked explicitly.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;When to use &lt;code&gt;init&lt;/code&gt; functions
&lt;ul&gt;
&lt;li&gt;They should not be used to initialize variables, as error handling is limited (they don&amp;rsquo;t return errors, one must &lt;code&gt;panic&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;When testing a package, the &lt;code&gt;init&lt;/code&gt; function is executed before the tests are run.&lt;/li&gt;
&lt;li&gt;If side effects are achieved via variables, these become global variables, which pollute the global namespace. Any function in the package can access these variables, which can lead to unexpected behavior. Unit tests depending on such functions will be harder to write.&lt;/li&gt;
&lt;li&gt;The responsibility of error handling should be left up to the caller.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;They can be helpful to define static configurations&lt;/em&gt;, as is done in &lt;a href="https://cs.opensource.google/go/x/website/&amp;#43;/e0d934b4:blog/blog.go;l=32"&gt;the &lt;code&gt;init&lt;/code&gt; function of the Go blog&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="4---overusing-getters-and-setters"&gt;4 - Overusing getters and setters&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Go has no support for automatic getters and setters, and it is idiomatic to use direct access to fields.&lt;/li&gt;
&lt;li&gt;When the use case justifies it, a getter should be named &lt;code&gt;FieldName&lt;/code&gt; (not &lt;code&gt;GetFieldName&lt;/code&gt;) and a setter should be named &lt;code&gt;SetFieldName&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="5---interface-pollution"&gt;5 - Interface pollution&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Go&amp;rsquo;s interfaces are satisfied implicitly (structural typing).&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;The bigger the interface, the weaker the abstraction.&amp;rdquo;&lt;/em&gt; — Rob Pike&lt;/li&gt;
&lt;li&gt;When to use interfaces
&lt;ul&gt;
&lt;li&gt;Common behavior
&lt;ul&gt;
&lt;li&gt;The behavior is factored out into an interface when it is common to multiple types (e.g., &lt;code&gt;sort.Interface&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Decoupling
&lt;ul&gt;
&lt;li&gt;The implementation is decoupled from the interface, which allows for easier testing and swapping of implementations.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Restricting behavior
&lt;ul&gt;
&lt;li&gt;The interface restricts the behavior of the type, which can be useful for enforcing constraints. For example, the &lt;code&gt;io.Reader&lt;/code&gt; interface restricts the behavior of the type to reading. It is possible to enforce constraints such as with a getter interface that only has a &lt;code&gt;Get&lt;/code&gt; method and prevents setting values.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Interface pollution
&lt;ul&gt;
&lt;li&gt;Abstractions should be discovered, not invented. Only create interfaces when they are needed.&lt;/li&gt;
&lt;li&gt;Adding levels of indirection makes the code harder to understand.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="6---interface-on-the-producer-side"&gt;6 - Interface on the producer side&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Producer side&lt;/em&gt; refers to an interface defined in the same package as the type that satisfies it.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Consumer side&lt;/em&gt; refers to an interface defined in a package that uses it and which is independent of the package that implements the interface.&lt;/li&gt;
&lt;li&gt;The client should determine the level of abstraction it needs.&lt;/li&gt;
&lt;li&gt;This relates to the &lt;em&gt;Interface segregation principle&lt;/em&gt; of SOLID.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="7---returning-interfaces"&gt;7 - Returning interfaces&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;In many cases, this is a bad practice in Go.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Be conservative in what you do, be liberal in what you accept from others.&amp;rdquo;&lt;/em&gt; — Transmission Control Protocol
&lt;ul&gt;
&lt;li&gt;Return structs instead of interfaces.&lt;/li&gt;
&lt;li&gt;Accept interfaces if possible.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Interfaces might be returned (e.g., &lt;code&gt;io.Reader&lt;/code&gt;), but this should be done with caution. It&amp;rsquo;s usually done when the interface itself is implemented on the producer side (e.g., standard library) and the consumer side (e.g., user code) is expected to use it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="8---any-says-nothing"&gt;8 - &lt;code&gt;any&lt;/code&gt; says nothing&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;interface{}&lt;/code&gt; type is a placeholder for &lt;code&gt;any&lt;/code&gt; type. &lt;code&gt;any&lt;/code&gt; is an alias for &lt;code&gt;interface{}&lt;/code&gt; since Go 1.18.&lt;/li&gt;
&lt;li&gt;It requires a type assertion to be used.&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;any&lt;/code&gt; does not convey any meaningful information about the type.&lt;/li&gt;
&lt;li&gt;It will lead to issues compilation-wise.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="9---being-confused-about-when-to-use-generics"&gt;9 - Being confused about when to use generics&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A &amp;ldquo;type parameter&amp;rdquo; is a placeholder for a type that is specified when the function is called.&lt;/li&gt;
&lt;li&gt;A constraint is a condition that the type parameter must satisfy (e.g., implement an interface like &lt;code&gt;comparable&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;go&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;intStringConstraint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;~&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;~&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ^--- union operator&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;The union operator (&lt;code&gt;|&lt;/code&gt;) is used to specify that the type parameter must be either &lt;code&gt;int&lt;/code&gt; or &lt;code&gt;string&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The tilde (&lt;code&gt;~&lt;/code&gt;) is used to restrict all the underlying types of the type parameter to the specified types.&lt;/li&gt;
&lt;li&gt;Common uses and misuses
&lt;ul&gt;
&lt;li&gt;Generics are useful to factor out common behavior (e.g, the &lt;code&gt;sort&lt;/code&gt; interface).
&lt;ul&gt;
&lt;li&gt;Data structures (e.g., binary tree, linked list, heap).&lt;/li&gt;
&lt;li&gt;Functions working with slice, map, channels of any type.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Generics are not useful when the behavior is not common.
&lt;ul&gt;
&lt;li&gt;If the method of the type argument is called, then the generic should be removed in the function received and a type should be specified in the function parameter.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Generics do not shine when they make the code more complex. Unnecessary abstraction should be avoided.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="10---not-being-aware-of-the-possible-problems-with-type-embedding"&gt;10 - Not being aware of the possible problems with type embedding&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A type embedding is a way to reuse the fields and methods of a type in another type. The fields from that other type get &amp;ldquo;promoted&amp;rdquo; to the new type (i.e., they can be accessed directly from the new type, as well as through the embedded type).&lt;/li&gt;
&lt;li&gt;Incorrect usage
&lt;ul&gt;
&lt;li&gt;An example of a wrong use of type embedding is a &lt;code&gt;sync.Mutex&lt;/code&gt; embedded in a struct. It will be available outside the struct and can be misused.&lt;/li&gt;
&lt;li&gt;It shouldn&amp;rsquo;t be used only as syntactic sugar to access the methods of the embedded type. Use a field instead.&lt;/li&gt;
&lt;li&gt;It shouldn&amp;rsquo;t promote fields that are not intended to be used outside the struct (e.g., &lt;code&gt;sync.Mutex&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Correct usage
&lt;ul&gt;
&lt;li&gt;A custom logger that wants to write and close may embed a &lt;code&gt;io.WriteCloser&lt;/code&gt; interface so that both methods are available. They will be promoted to the new type and there won&amp;rsquo;t be a need to implement these methods simply to forward a call to the embedded type.&lt;/li&gt;
&lt;li&gt;Preventing type embeddings in public structs is probably the safest route.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Embedding vs. OOP subclassing
&lt;ul&gt;
&lt;li&gt;Embedding is not subclassing. It is a way to reuse fields and methods of a type in another type. It is a form of composition.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="11---not-using-the-functional-options-pattern"&gt;11 - Not using the functional options pattern&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Config struct
&lt;ul&gt;
&lt;li&gt;This is limited because to bring flexibility and distinguish between the zero value and the default value, one must use pointers.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Builder pattern
&lt;ul&gt;
&lt;li&gt;To keep chaining method calls, one should not return an error, which means validation is delayed.&lt;/li&gt;
&lt;li&gt;To have proper error handling along the way, it makes the configuration more complex than it needs to be.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Functional options pattern
&lt;ul&gt;
&lt;li&gt;This pattern makes use of closures to set the configuration options, where the closure is an anonymous function being returned.&lt;/li&gt;
&lt;li&gt;The closure is called with the configuration struct as an argument, and it sets the configuration options.&lt;/li&gt;
&lt;li&gt;This pattern allows for validation of the configuration options and for the configuration to be set in a single call.&lt;/li&gt;
&lt;li&gt;It is a flexible pattern that allows for the configuration to be set in any order and for the configuration to be validated at the time of setting.&lt;/li&gt;
&lt;li&gt;One does not need to provide a default value for the configuration struct since the function accepts variadic arguments, which may be empty.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="12---project-misorganization"&gt;12 - Project misorganization&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Project structure
&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://github.com/golang-standards/project-layout"&gt;&lt;code&gt;project-layout&lt;/code&gt;&lt;/a&gt; is a great place to start for a complex application. Despite not being an official standard, it is a great and logically sound starting point.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Package organization
&lt;ul&gt;
&lt;li&gt;There is no concept of subpackages in Go. Sub-directories are useful to have high cohesion.&lt;/li&gt;
&lt;li&gt;Hexagonal architecture group per technical layer.&lt;/li&gt;
&lt;li&gt;An alternative is to group by context (e.g., customer context, contract context).&lt;/li&gt;
&lt;li&gt;Avoid both nano and huge packages.
&lt;ul&gt;
&lt;li&gt;Nano packages probably mean there&amp;rsquo;s lack of understanding about the connections between the different files.&lt;/li&gt;
&lt;li&gt;Huge packages dilute the meaning of the package.&lt;/li&gt;
&lt;li&gt;Name a package after what they &lt;em&gt;provide&lt;/em&gt;, not what they &lt;em&gt;contain&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Package names should be short, concise, expressive and, by convention, a single lowercase word&amp;rdquo;&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Minimize what should be exported as much as possible to reduce coupling.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="13---creating-utility-packages"&gt;13 - Creating utility packages&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;utils&lt;/code&gt;, &lt;code&gt;common&lt;/code&gt;, &lt;code&gt;base&lt;/code&gt;&amp;hellip; bad practice.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;util&lt;/code&gt; is meaningless.&lt;/li&gt;
&lt;li&gt;While nano packages should be avoided, if a few functions are highly cohesive and don&amp;rsquo;t belong anywhere else, it&amp;rsquo;s a good idea to keep them separate with good naming.&lt;/li&gt;
&lt;li&gt;Make APIs expressive.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="14---ignoring-package-name-collisions"&gt;14 - Ignoring package name collisions&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Naming a variable the same as a package name will shadow the package name.&lt;/li&gt;
&lt;li&gt;Either rename the variable (&lt;code&gt;redisClient := redis.NewClient()&lt;/code&gt;) or rename the import with an alias (&lt;code&gt;import redisapi &amp;quot;mylib/redis&amp;quot;&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;The same is true for built-in functions like &lt;code&gt;copy&lt;/code&gt;: they shouldn&amp;rsquo;t be shadowed.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="15---missing-code-documentation"&gt;15 - Missing code documentation&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Simplifies how clients consume an API.&lt;/li&gt;
&lt;li&gt;Helps in maintaining a project.&lt;/li&gt;
&lt;li&gt;In Go, every exported element should be documented, starting with the name of the element being documented (e.g., &lt;code&gt;Customer is ...&lt;/code&gt;). It should be a complete sentence ending with a period.&lt;/li&gt;
&lt;li&gt;An element can be marked as deprecated with a comment starting with &lt;code&gt;Deprecated:&lt;/code&gt; after the description of the element.&lt;/li&gt;
&lt;li&gt;The convention to document a package is to have a comment at the top of the file with the package name and a description of the package, starting with &lt;code&gt;// Package packageName ...&lt;/code&gt;. The first line should be concise, then a blank line, then a more detailed description.&lt;/li&gt;
&lt;li&gt;Comments at the top of the file will be omitted from the documentation if they are followed by a blank line and more comments.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="16---not-using-linters"&gt;16 - Not using linters&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Automatic tool to analyze code and catch errors.&lt;/li&gt;
&lt;li&gt;They help prevent mistakes automatically and consistently.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="data-types"&gt;Data types&lt;/h2&gt;
&lt;h3 id="17---creating-confusion-with-octal-literals"&gt;17 - Creating confusion with octal literals&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Using &lt;code&gt;0o&lt;/code&gt; instead of &lt;code&gt;0&lt;/code&gt; for octal literals is clearer.&lt;/li&gt;
&lt;li&gt;Octal is still useful to express file permissions (e.g., &lt;code&gt;0644&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Go supports using underscores in numeric literals to improve readability (e.g., &lt;code&gt;1_000_000&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="18---neglecting-integer-overflows"&gt;18 - Neglecting integer overflows&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;It won&amp;rsquo;t panic at runtime. They are silent bugs.&lt;/li&gt;
&lt;li&gt;It will wrap around.&lt;/li&gt;
&lt;li&gt;Use the &lt;code&gt;math&lt;/code&gt; package to check for overflows.&lt;/li&gt;
&lt;li&gt;Detecting integer overflow when incrementing
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;math.MaxInt&lt;/code&gt;, &lt;code&gt;math.MinInt&lt;/code&gt;, &lt;code&gt;math.MaxUint&lt;/code&gt;, &lt;code&gt;math.MinUint&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Detecting integer overflows during addition
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;math.MaxInt - a &amp;lt; b&lt;/code&gt; or &lt;code&gt;math.MaxUint - a &amp;lt; b&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Detecting integer overflows during multiplication
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;math.MaxInt/a &amp;lt; b&lt;/code&gt; or &lt;code&gt;math.MaxUint/a &amp;lt; b&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="19---not-understanding-floating-points"&gt;19 - Not understanding floating points&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;em&gt;mantissa&lt;/em&gt; is the significant part of a floating-point number.&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;exponent&lt;/em&gt; is the power of 10 by which the mantissa is multiplied.&lt;/li&gt;
&lt;li&gt;For &lt;code&gt;float32&lt;/code&gt;, the mantissa is 23 bits and the exponent is 8 bits.&lt;/li&gt;
&lt;li&gt;For &lt;code&gt;float64&lt;/code&gt;, the mantissa is 52 bits and the exponent is 11 bits, the remaining bit is the sign bit.&lt;/li&gt;
&lt;li&gt;It is best to avoid comparing by equality with floating-point numbers and instead use a tolerance (delta).&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;math.IsInf&lt;/code&gt; and &lt;code&gt;math.IsNaN&lt;/code&gt; to check for infinity and NaN.&lt;/li&gt;
&lt;li&gt;Group floating-point numbers together by their magnitude to reduce precision loss when adding and subtracting.&lt;/li&gt;
&lt;li&gt;With calculations involving both addition/subtraction and multiplication/division, it is best to perform the multiplication/division first to reduce precision loss.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="20---not-understanding-slice-length-and-capacity"&gt;20 - Not understanding slice length and capacity&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Slice data is stored contiguously in an array data structure.&lt;/li&gt;
&lt;li&gt;A slice holds a pointer to the array, the length of the slice, and the capacity of the slice.&lt;/li&gt;
&lt;li&gt;Trying to access an index greater than the length of the slice will result in a runtime panic, even though the capacity is greater.&lt;/li&gt;
&lt;li&gt;Slicing a slice will create a new slice with the same underlying array, but with a different length and capacity.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;append&lt;/code&gt; function will create a new slice with a new underlying array if the capacity is exceeded.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="21---inefficient-slice-initialization"&gt;21 - Inefficient slice initialization&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;make&lt;/code&gt; to initialize a slice with a specific length and capacity.&lt;/li&gt;
&lt;li&gt;Set a length of &lt;code&gt;0&lt;/code&gt; (2nd argument to &lt;code&gt;make&lt;/code&gt;) and a capacity of &lt;code&gt;n&lt;/code&gt; (3rd argument to &lt;code&gt;make&lt;/code&gt;), which will reuse the same backing array until the capacity is exceeded.&lt;/li&gt;
&lt;li&gt;One can also set only the length and use direct assignment instead of &lt;code&gt;append&lt;/code&gt; (&lt;code&gt;bars[i] = fooToBar(foo)&lt;/code&gt;). This is about 400% faster than using an empty slice.&lt;/li&gt;
&lt;li&gt;If the length of the slice is already known, it is better to take advantage of it and set the length of the slice when initializing it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="22---being-confused-about-nil-vs-empty-slices"&gt;22 - Being confused about nil vs. empty slices&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A slice is empty if its length is equal to 0.&lt;/li&gt;
&lt;li&gt;A slice is &lt;code&gt;nil&lt;/code&gt; if it equals &lt;code&gt;nil&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;nil&lt;/code&gt; slice is an empty slice.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;nil&lt;/code&gt; slice can be passed in a single line: &lt;code&gt;s := append([]int(nil), 42)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A nil slice is marshaled as a &lt;code&gt;null&lt;/code&gt; element, whereas a non-nil, empty slice is marshaled as an empty array.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="23---not-properly-checking-if-a-slice-is-empty"&gt;23 - Not properly checking if a slice is empty&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;quot;[&amp;hellip;] when designing interfaces, we should avoid distinguishing nil and empty slices, which leads to subtle programming errors&amp;quot;&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;This principle is the same with maps. To check if a map is empty, check its length, not whether it&amp;rsquo;s nil&amp;rdquo;&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Do &lt;code&gt;len(slice) != 0&lt;/code&gt; instead of &lt;code&gt;slice != nil&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="24---not-making-slice-copies-correctly"&gt;24 - Not making slice copies correctly&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The destination slice must have the same length as the source slice (or greater). Else, the copy will match the length of the destination slice and may be missing elements.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;copy&lt;/code&gt; is the more idiomatic way to copy slices (vs. &lt;code&gt;dst := append([]int(nil), src...)&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="25---unexpected-side-effects-using-slice-append"&gt;25 - Unexpected side effects using slice &lt;code&gt;append&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;If the resulting slice as a length smaller than the capacity, the slice will be modified in place and will have unintended side effects.&lt;/li&gt;
&lt;li&gt;To avoid side effects outside a given range of a slice, &lt;em&gt;full slice expression&lt;/em&gt; can be used (e.g., &lt;code&gt;s := []int{1, 2, 3}&lt;/code&gt;, then &lt;code&gt;s[:2:2]&lt;/code&gt; will create a new slice with a length of 2 and a capacity of 2).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="26---slices-and-memory-leaks"&gt;26 - Slices and memory leaks&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Leaking capacity
&lt;ul&gt;
&lt;li&gt;Slicing a huge slice to a smaller slice will keep the capacity of the original slice. This makes matters worst if the result of a slice is kept for many iterations in a loop.&lt;/li&gt;
&lt;li&gt;Instead, use &lt;code&gt;copy&lt;/code&gt; to create a new slice with the necessary length.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Leakage with full slice expressions
&lt;ul&gt;
&lt;li&gt;The issue is the same as with slicing.&lt;/li&gt;
&lt;li&gt;The solution is the same as with slicing.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Slice and pointers
&lt;ul&gt;
&lt;li&gt;A slice holds a pointer to an array, so if the slice is kept, the array will be kept in memory.&lt;/li&gt;
&lt;li&gt;To avoid this, use &lt;code&gt;copy&lt;/code&gt; to create a new slice with the necessary length.&lt;/li&gt;
&lt;li&gt;If however we want to keep the original capacity, we can set all the unneeded elements to &lt;code&gt;nil&lt;/code&gt; explicitly. For instance, all elements but the first 2:&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;go&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;foos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;foos&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 id="27---inefficient-map-initialization"&gt;27 - Inefficient map initialization&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Maps are unordered collections of key-value pairs, where all keys are unique. They use hash tables under the hood.&lt;/li&gt;
&lt;li&gt;Insertion can be an &lt;code&gt;O(n)&lt;/code&gt; operation if the map is resized.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;make&lt;/code&gt; can be used to initialize a map with a specific size (second argument to &lt;code&gt;make&lt;/code&gt;, without capacity). This avoids having to grow the map and re-balancing all the buckets of 8 elements each.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="28---maps-and-memory-leaks"&gt;28 - Maps and memory leaks&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The number of buckets in a map cannot shrink. Removing elements from a map will not reduce the number of buckets.&lt;/li&gt;
&lt;li&gt;Even the overflown buckets will not be reduced.&lt;/li&gt;
&lt;li&gt;One possible solution: periodically copy the map to a new map.
&lt;ul&gt;
&lt;li&gt;Downside: the amount of memory used will be doubled for a short period of time.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Another solution: update the value type of the map to store an array pointer.
&lt;ul&gt;
&lt;li&gt;The peak memory consumed will be far less.&lt;/li&gt;
&lt;li&gt;The garbage collection will be more efficient.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="29---comparing-values-incorrectly"&gt;29 - Comparing values incorrectly&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;==&lt;/code&gt; does not work for slices, maps, and functions.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reflect.DeepEqual&lt;/code&gt; could be used, but it&amp;rsquo;s about 100x slower than &lt;code&gt;==&lt;/code&gt;, and so might be better used for testing only.&lt;/li&gt;
&lt;li&gt;If strict comparison is needed on structs and good performance is required, consider using a custom comparison function.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="control-structures"&gt;Control structures&lt;/h2&gt;
&lt;h3 id="30---ignoring-the-fact-that-elements-are-copied-in-range-loops"&gt;30 - Ignoring the fact that elements are copied in range loops&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;In Go, every assignment leads to a copy of the value.&lt;/li&gt;
&lt;li&gt;In a range loop, the value is copied, not the reference.&lt;/li&gt;
&lt;li&gt;To update a slice with a range loop, one must use the index to update the slice, i.e., &lt;code&gt;for i := range s { s[i] = ... }&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A classic &lt;code&gt;for&lt;/code&gt; loop can be used to update the slice in place, i.e., &lt;code&gt;for i := 0; i &amp;lt; len(s); i++ { s[i] = ... }&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Using a pointer to a slice will allow for the slice to be updated in place, but iterating over such a slice is less efficient for the CPU.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="31---ignoring-how-arguments-are-evaluated-in-range-loops"&gt;31 - Ignoring how arguments are evaluated in range loops&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Looping over an array will copy the value of the array element.&lt;/li&gt;
&lt;li&gt;Using a pointer to the array will allow for the array to be updated in place and will avoid copying values, which may be more efficient, especially on large arrays.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;range&lt;/code&gt; loop evaluates the expression to its right only once, so if the expression is a slice, the slice will not be updated in the loop.&lt;/li&gt;
&lt;li&gt;To access updated elements of a slice during iteration with a &lt;code&gt;range&lt;/code&gt; loop, one must use the index to access the element in the slice, not the value of the element, since the expression is copied to a temporary variable.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="32---ignoring-the-impact-of-using-pointer-elements-in-range-loops"&gt;32 - Ignoring the impact of using pointer elements in range loops&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Using a pointer to a slice when looping with a &lt;code&gt;range&lt;/code&gt; loop can lead to storing the address of the temporary variable in the slice for all elements in the slice.&lt;/li&gt;
&lt;li&gt;To avoid this issue, one can create a local variable for each element to be iterated over in the slice and then use the local variable in the loop to perform assignments.&lt;/li&gt;
&lt;li&gt;Another solution is to use a &lt;code&gt;range&lt;/code&gt; loop with the index, create a temporary variable by dereferencing the pointer, and then assign the temporary variable to the slice.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="33---making-wrong-assumptions-during-map-iterations"&gt;33 - Making wrong assumptions during map iterations&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Maps don&amp;rsquo;t sort keys.&lt;/li&gt;
&lt;li&gt;Insertion order is not preserved.&lt;/li&gt;
&lt;li&gt;Iteration order is unspecified. This is a conscious language design choice so that developers don&amp;rsquo;t rely on the order of the keys.&lt;/li&gt;
&lt;li&gt;One should work on a copy of a map if new elements are to be added or removed during iteration.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="34---ignoring-how-the-break-statement-works"&gt;34 - Ignoring how the break statement works&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;break&lt;/code&gt; works on the innermost loop, whether that be a &lt;code&gt;for&lt;/code&gt;, &lt;code&gt;switch&lt;/code&gt;, or &lt;code&gt;select&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;To break outside of a loop, one can use a label.&lt;/li&gt;
&lt;li&gt;A label can be an idiomatic way to break out of nested loops, as is done in the &lt;code&gt;net/http&lt;/code&gt; package with a &lt;code&gt;readlines:&lt;/code&gt; label.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="35---using-defer-inside-a-loop"&gt;35 - Using &lt;code&gt;defer&lt;/code&gt; inside a loop&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;defer&lt;/code&gt; is a statement that schedules a function to be called when the surrounding function returns. That is, it won&amp;rsquo;t run on each iteration of the loop until it returns, if ever.&lt;/li&gt;
&lt;li&gt;To avoid resource leaks like this, each iteration of the loop can use a separate function that is run and defers the cleanup.&lt;/li&gt;
&lt;li&gt;Another solution is to use a closure, but this can be less readable.&lt;/li&gt;
&lt;li&gt;A function call will decrease performance slightly, so if performance is a concern, the &lt;code&gt;defer&lt;/code&gt; call should be handled manually before the loop.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="strings"&gt;Strings&lt;/h2&gt;
&lt;h3 id="36---not-understanding-the-concept-of-a-rune"&gt;36 - Not understanding the concept of a rune&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A charset is a set of characters.&lt;/li&gt;
&lt;li&gt;An encoding describes how to translate a character set into a sequence of bytes.&lt;/li&gt;
&lt;li&gt;In Go, a string is a reference to an immutable slice of bytes.&lt;/li&gt;
&lt;li&gt;A rune is a Unicode code point.&lt;/li&gt;
&lt;li&gt;Source code in Go is encoded in UTF-8: string literals will then also be encoded in UTF-8.&lt;/li&gt;
&lt;li&gt;UTF-8 is a variable-length encoding, where each code point is represented by one to four bytes. For instance, the string &lt;code&gt;hello&lt;/code&gt; is represented by five bytes, but the string containing the character &lt;code&gt;汉&lt;/code&gt; is represented by three bytes, which we can check with &lt;code&gt;len(&amp;quot;汉&amp;quot;)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A rune is an alias for &lt;code&gt;int32&lt;/code&gt; (4 bytes = 32 bits).&lt;/li&gt;
&lt;li&gt;The repository &lt;code&gt;golang.org/x&lt;/code&gt; contains extensions to work with UTF-16 and UTF-32.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="37---inaccurate-string-iteration"&gt;37 - Inaccurate string iteration&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;With the &lt;code&gt;unicode/utf8&lt;/code&gt; package, one can use &lt;code&gt;utf8.RuneCountInString(s)&lt;/code&gt; to count the number of runes in a string.&lt;/li&gt;
&lt;li&gt;Accessing specific runes can be done without converting the string if characters are in the ASCII range.&lt;/li&gt;
&lt;li&gt;It is most useful to convert a string to a slice of runes when a specific rune is required by index.&lt;/li&gt;
&lt;li&gt;Iterating over the runes with a &lt;code&gt;for range&lt;/code&gt; loop returns the starting index of the rune and the rune itself.&lt;/li&gt;
&lt;li&gt;Iterating with a &lt;code&gt;for range&lt;/code&gt; using only the index returns the starting index of the rune and the byte value at that position.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="38---misusing-trim-functions"&gt;38 - Misusing trim functions&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;TrimRight&lt;/code&gt; to remove trailing characters. Go backwards until a rune is not in the set of characters to be trimmed.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;TrimSuffix&lt;/code&gt; to remove a suffix (only one substring occurrence).&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;TrimLeft&lt;/code&gt; to remove leading characters. Go forwards until a rune is not in the set of characters to be trimmed.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;TrimPrefix&lt;/code&gt; to remove a prefix (only one substring occurrence).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="39---under-optimized-string-concatenation"&gt;39 - Under-optimized string concatenation&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;+=&lt;/code&gt; is inefficient for string concatenation because it creates a new string each time.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;strings.Builder&lt;/code&gt; for efficient string concatenation.&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;Grow&lt;/code&gt; on the builder to allocate memory for the string.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="40---useless-string-conversions"&gt;40 - Useless string conversions&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Working with &lt;code&gt;[]byte&lt;/code&gt; is more efficient than working with strings due to many interfaces in the standard library taking &lt;code&gt;[]byte&lt;/code&gt; as input, which avoids doing a conversion.&lt;/li&gt;
&lt;li&gt;Most methods in the &lt;code&gt;strings&lt;/code&gt; package have a &lt;code&gt;[]byte&lt;/code&gt; counterpart in the &lt;code&gt;bytes&lt;/code&gt; package.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="41---substrings-and-memory-leaks"&gt;41 - Substrings and memory leaks&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A substring shares the same backing array as the original string.&lt;/li&gt;
&lt;li&gt;If the substring is kept, the original string will be kept in memory.&lt;/li&gt;
&lt;li&gt;To avoid this, either use &lt;code&gt;string([]byte(theString[:n]))&lt;/code&gt; to slice the needed part of the string or &lt;code&gt;strings.Clone&lt;/code&gt; to create a new string.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="functions-and-methods"&gt;Functions and methods&lt;/h2&gt;
&lt;h3 id="42---not-knowing-which-type-of-receiver-to-use"&gt;42 - Not knowing which type of receiver to use&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Go does not pass values by reference.&lt;/li&gt;
&lt;li&gt;A value receiver makes a copy of the value: changes are local to the method.&lt;/li&gt;
&lt;li&gt;A pointer receiver passes the memory address of the value: changes are global to the value. Strictly speaking, this is still a copy, but the copy is made for a pointer.&lt;/li&gt;
&lt;li&gt;Use a pointer receiver when:
&lt;ul&gt;
&lt;li&gt;The method needs to mutate the receiver.&lt;/li&gt;
&lt;li&gt;The method receiver contains a field that cannot be copied.&lt;/li&gt;
&lt;li&gt;The receiver is a large struct.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Use a value receiver when:
&lt;ul&gt;
&lt;li&gt;Enforcing immutability.&lt;/li&gt;
&lt;li&gt;The receiver is a &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;func&lt;/code&gt; or &lt;code&gt;chan&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A slice that doesn&amp;rsquo;t have to be mutated.&lt;/li&gt;
&lt;li&gt;The receiver is a small struct or array with no mutable fields, like &lt;code&gt;time.Time&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The receiver is a basic type, like &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;float64&lt;/code&gt;, or &lt;code&gt;string&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Mixing value and pointer receivers can lead to confusion and bugs: this should generally be avoided (although the standard library shows some exceptions, like &lt;code&gt;time.Time&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Defaulting to a value receiver is a good practice, unless there is a good reason to use a pointer receiver.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="43---never-using-named-result-parameters"&gt;43 - Never using named result parameters&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Only use named result parameters when they make the code more readable (e.g., returning two &lt;code&gt;int&lt;/code&gt; can be ambiguous).&lt;/li&gt;
&lt;li&gt;Use naked returns only when the function is short and the return values are clear.&lt;/li&gt;
&lt;li&gt;For an interface definition, named result parameters are useful to document the return values.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="44---unintended-side-effects-with-named-result-parameters"&gt;44 - Unintended side effects with named result parameters&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Named result parameters are &lt;em&gt;initialized to their zero value&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Using named result parameters doesn&amp;rsquo;t mean one has to use naked returns: clarity is more important than brevity.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="45---returning-a-nil-receiver"&gt;45 - Returning a nil receiver&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A nil receiver is a pointer to a struct that has not been initialized.&lt;/li&gt;
&lt;li&gt;Instead of returning a pointer to a struct (which will return a non-nil pointer to a nil struct), return &lt;code&gt;nil&lt;/code&gt; directly.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="46---using-a-filename-as-a-function-input"&gt;46 - Using a filename as a function input&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;It is more idiomatic and flexible to use an &lt;code&gt;io.Reader&lt;/code&gt; or &lt;code&gt;io.Writer&lt;/code&gt; as an input to a function that reads or writes to a file: this abstracts the data source.&lt;/li&gt;
&lt;li&gt;This allows for the function to be used with any type that implements the &lt;code&gt;io.Reader&lt;/code&gt; or &lt;code&gt;io.Writer&lt;/code&gt; interface, not just a file.&lt;/li&gt;
&lt;li&gt;It makes testing easier, as one can pass in a &lt;code&gt;bytes&lt;/code&gt; buffer instead of a file, for instance.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="47---ignoring-how-defer-arguments-and-receivers-are-evaluated"&gt;47 - Ignoring how defer arguments and receivers are evaluated&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The arguments to a &lt;code&gt;defer&lt;/code&gt; statement are evaluated when the &lt;code&gt;defer&lt;/code&gt; statement is executed, not when the deferred function is executed.
&lt;ul&gt;
&lt;li&gt;That means that if a variable is initialized with a zero value and passed to &lt;code&gt;defer&lt;/code&gt; as is, the deferred function will receive the zero value.&lt;/li&gt;
&lt;li&gt;To avoid this pitfall, one can use a pointer to the variable, which will be evaluated when the deferred function is executed. The memory address will not change, but the value at that address will.&lt;/li&gt;
&lt;li&gt;Another solution is to use a closure to capture the value of the variable at the time the &lt;code&gt;defer&lt;/code&gt; statement is executed.
&lt;ul&gt;
&lt;li&gt;This works because the values of the variables are evaluated when the closure runs, not when the closure is created.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;In the case of a method receiver, data in the receiver is evaluated when the &lt;code&gt;defer&lt;/code&gt; statement is executed, leading to the same situation as with arguments.
&lt;ul&gt;
&lt;li&gt;A pointer receiver can be used to avoid this issue, as the updated value of the pointer will be visible to the deferred function when it is executed.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="error-management"&gt;Error management&lt;/h2&gt;
&lt;h3 id="48---panicking"&gt;48 - Panicking&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;It is used to signal genuinely unexpected errors, such as programming errors (e.g., &lt;code&gt;net/http&lt;/code&gt; checking for a status code outside the valid range).&lt;/li&gt;
&lt;li&gt;Another use case for &lt;code&gt;panic&lt;/code&gt; is when the application requires a dependency but fails to initialize it (e.g., a regular expression).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="49---ignoring-when-to-wrap-an-error"&gt;49 - Ignoring when to wrap an error&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;%w&lt;/code&gt; is used to wrap an error conveniently.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;%v&lt;/code&gt; directive will print the transformed error message, but &lt;code&gt;%w&lt;/code&gt; will print the error message and the wrapped error message.&lt;/li&gt;
&lt;li&gt;If a caller relies on unwrapping an error provided with &lt;code&gt;%w&lt;/code&gt;, it leads to coupling: if the underlying implementation that is wrapped changes, the caller will break.&lt;/li&gt;
&lt;li&gt;To mark an error, one should use a custom error type.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="50---checking-an-error-type-inaccurately"&gt;50 - Checking an error type inaccurately&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;errors.As&lt;/code&gt; is used to check if an error is of a specific type anywhere in the error chain.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;errors.As&lt;/code&gt; should be used whenever error wrapping is used.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="51---checking-an-error-value-inaccurately"&gt;51 - Checking an error value inaccurately&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A sentinel error is a predefined error that can be compared to an error value. It is about an &amp;ldquo;expected&amp;rdquo; error (e.g., &lt;code&gt;sql.ErrNoRows&lt;/code&gt; and &lt;code&gt;io.EOF&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Unexpected errors should be implemented as error types, implementing the &lt;code&gt;error&lt;/code&gt; interface.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;errors.Is&lt;/code&gt; is used to check if an error is equal to a sentinel error (not &lt;code&gt;==&lt;/code&gt;! if the error is wrapped, it will not be equal to the sentinel error).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="52---handling-an-error-twice"&gt;52 - Handling an error twice&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Logging the same error twice can lead to confusion.&lt;/li&gt;
&lt;li&gt;Logging an error is handling an error! One should not both log and return an error.&lt;/li&gt;
&lt;li&gt;Error wrapping is useful to provide context to an error when propagating it up the call stack.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="53---not-handling-an-error"&gt;53 - Not handling an error&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The only way to explicitly ignore an error is to assign it to the blank identifier &lt;code&gt;_&lt;/code&gt;. Adding a comment about why the error is ignored is a good practice.&lt;/li&gt;
&lt;li&gt;Do not simply ignore the assignment.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="54---not-handling-defer-errors"&gt;54 - Not handling defer errors&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Errors should be handled or explicitly ignored in &lt;code&gt;defer&lt;/code&gt; functions.&lt;/li&gt;
&lt;li&gt;To return an error to the caller via the &lt;code&gt;defer&lt;/code&gt; function, one can use a named return parameter, assigning the error to it.&lt;/li&gt;
&lt;li&gt;Instead of returning multiple errors from a &lt;code&gt;defer&lt;/code&gt; function, it may be a good idea to log one error and return the other, depending on the context.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="concurrency-foundations"&gt;Concurrency: Foundations&lt;/h2&gt;
&lt;h3 id="55---mixing-up-concurrency-and-parallelism"&gt;55 - Mixing up concurrency and parallelism&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Concurrency is about structuring a program, while parallelism is about running multiple tasks at the same time.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Concurrency enables parallelism&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="56---thinking-concurrency-is-always-faster"&gt;56 - Thinking concurrency is always faster&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A thread is the smallest unit of execution that can be scheduled by an operating system.&lt;/li&gt;
&lt;li&gt;A goroutine is a lightweight thread managed by the Go runtime: it is context-switched on and off by an OS thread.&lt;/li&gt;
&lt;li&gt;A goroutine has a memory footprint of 2 KB, while a thread has a memory footprint of ~2 MB.&lt;/li&gt;
&lt;li&gt;There is a point of diminishing returns when creating too many goroutines: one must take into account the overhead of context switching and determine the size of the workload that will be processed by each goroutine.&lt;/li&gt;
&lt;li&gt;It is often best to start with a sequential solution and use profiling tools to determine where to add concurrency.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="57---being-puzzled-about-when-to-use-channels-or-mutexes"&gt;57 - Being puzzled about when to use channels or mutexes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Synchronization between parallel goroutines is achieved with mutexes (mutex stands for &lt;em&gt;mutual exclusion&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;Channels are used to communicate between goroutines. If there are 3 goroutines and a third one needs results from the first two, a channel is the way to go.&lt;/li&gt;
&lt;li&gt;Mutexes ensure exclusive access to a resource (e.g., a map).&lt;/li&gt;
&lt;li&gt;Coordination or ownership of a resource is achieved with channels.&lt;/li&gt;
&lt;li&gt;In general, mutexes are needed for parallel goroutines, whereas channels are needed for concurrent ones.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="58---not-understanding-race-problems"&gt;58 - Not understanding race problems&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;em&gt;data race&lt;/em&gt; occurs when two goroutines access the same variable concurrently and at least one of the accesses is a write.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sync/atomic&lt;/code&gt; provides atomic operations to avoid data races. It works only on primitive types.&lt;/li&gt;
&lt;li&gt;Channels can be used to perform atomic operations on non-primitive types, then send the result over the channel.&lt;/li&gt;
&lt;li&gt;Mutexes can be used to protect a critical section of code.&lt;/li&gt;
&lt;li&gt;The absence of a data race doesn&amp;rsquo;t mean the code will be deterministic: it is possible for goroutines to be scheduled in different orders, leading to a &lt;em&gt;race condition&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;A buffered channel can lead to a data race because a receive from an unbuffered channel happens before the send, but a send to a buffered channel happens before the receive.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="59---not-understanding-the-concurrency-impacts-of-a-workload-type"&gt;59 - Not understanding the concurrency impacts of a workload type&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A worker pool can be based on &lt;code&gt;GOMAXPROCS&lt;/code&gt; (the number of OS threads) to efficiently distribute the workload when a workload is CPU-bound.&lt;/li&gt;
&lt;li&gt;When a workload is I/O-bound, the number of goroutines can be increased to handle more I/O operations concurrently, depending on the external systems.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="60---misunderstanding-go-contexts"&gt;60 - Misunderstanding Go contexts&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A context is a way to pass deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;context.Background&lt;/code&gt; is the root context, which is never canceled, has no values, and has no deadline, used when there is no parent context.&lt;/li&gt;
&lt;li&gt;Deadline
&lt;ul&gt;
&lt;li&gt;A deadline is a point in time when the context should be canceled, specified as either a &lt;code&gt;time.Time&lt;/code&gt; or a &lt;code&gt;time.Duration&lt;/code&gt; (time from now).&lt;/li&gt;
&lt;li&gt;Deferring the cancellation of a context can prevent a goroutine from being blocked indefinitely or leaking resources.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Cancellation signals
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;context.WithCancel&lt;/code&gt; returns a copy of the parent context with a new &lt;code&gt;Done&lt;/code&gt; channel.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Context values
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;context.WithValue&lt;/code&gt; returns a copy of the parent context with a key-value pair.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;A function that users wait for should take a context as an argument.&lt;/li&gt;
&lt;li&gt;Prefer &lt;code&gt;context.TODO()&lt;/code&gt; to &lt;code&gt;context.Background()&lt;/code&gt; when a context is needed but there is no parent context, as it better represents the intent.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="concurrency-practice"&gt;Concurrency: Practice&lt;/h2&gt;
&lt;h3 id="61---propagating-an-inappropriate-context"&gt;61 - Propagating an inappropriate context&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Propagating a context is about passing a context from one goroutine to another.&lt;/li&gt;
&lt;li&gt;A context should be passed to a goroutine carefully: if the context requires some values to be passed but the parent goroutine gets its context cancelled, the child goroutine will also be cancelled. This may be undesirable.
&lt;ul&gt;
&lt;li&gt;To avoid this, create a custom struct implementing a context that never expires and that does not carry a cancellation signal, so the values can be passed without the risk of being cancelled.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="62---starting-a-goroutine-without-knowing-when-to-stop-it"&gt;62 - Starting a goroutine without knowing when to stop it&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A goroutine might loop over the values received from a channel, but if the channel is never closed, this will be a resource leak.&lt;/li&gt;
&lt;li&gt;Passing a context to a goroutine (e.g., to expect a file to be closed) is bad design. Instead, the parent goroutine should be responsible for closing the file by calling a &lt;code&gt;defer&lt;/code&gt; function right after the resource used by another routine is created.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="63---not-being-careful-with-goroutines-and-loop-variables"&gt;63 - Not being careful with goroutines and loop variables&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Spinning up a goroutine inside a &lt;code&gt;for&lt;/code&gt; loop and relying on the loop variable can lead to unexpected behavior.&lt;/li&gt;
&lt;li&gt;Either assign the value of the loop variable to a new variable inside the loop or pass the loop variable as an argument to the goroutine. In the first case, the goroutine could be set up as a closure. In the second case, it would be a regular function call on an anonymous function.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="64---expecting-deterministic-behavior-using-select-and-channels"&gt;64 - Expecting deterministic behavior using select and channels&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;select&lt;/code&gt; statement is used to wait on multiple channels. If multiple channels are ready, one will be chosen semi-randomly.&lt;/li&gt;
&lt;li&gt;Using an unbuffered channel with a &lt;code&gt;select&lt;/code&gt; statement will block until a value is sent on the channel.&lt;/li&gt;
&lt;li&gt;Using a single channel and then checking the message type is an option.&lt;/li&gt;
&lt;li&gt;Using a &lt;code&gt;default&lt;/code&gt; case in a &lt;code&gt;select&lt;/code&gt; statement will allow the program to continue if a channel no longer receives values.
&lt;ul&gt;
&lt;li&gt;This approach works well for multiple channels: when a &lt;code&gt;select&lt;/code&gt; case is chosen, add an inner &lt;code&gt;select&lt;/code&gt; statement to handle the channel. For instance, after a &lt;em&gt;disconnection&lt;/em&gt; message, handle all remaining messages sent the another channel by looping over the channel inside that &lt;code&gt;select&lt;/code&gt; case.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="65---not-using-notification-channels"&gt;65 - Not using notification channels&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A notification channel is a channel that is used to signal the completion of a task.&lt;/li&gt;
&lt;li&gt;If no data is needed to be sent, a &lt;code&gt;struct{}&lt;/code&gt; can be sent over the channel. This is clearer than sending a &lt;code&gt;bool&lt;/code&gt; or &lt;code&gt;int&lt;/code&gt;. An empty struct occupies no memory.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="66---not-using-nil-channels"&gt;66 - Not using nil channels&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Sending a message to a &lt;code&gt;nil&lt;/code&gt; channel will block forever.&lt;/li&gt;
&lt;li&gt;Receiving from a closed channel is a non-blocking operation: the zero value of the channel type will be returned.&lt;/li&gt;
&lt;li&gt;When looping over multiple channels and using &lt;code&gt;select&lt;/code&gt;, if one of the channels is closed, this will lead to a busy-wait loop.
&lt;ul&gt;
&lt;li&gt;E.g., say we have two channels in a function that are to be &amp;ldquo;merged&amp;rdquo; such that data returned from that function comes from the two channels. If one of the channels is closed, the function will still keep looping over the now invalid &lt;code&gt;select&lt;/code&gt; case.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;To avoid this, one can use a &lt;code&gt;nil&lt;/code&gt; channel to signal a channel is closed. The &lt;code&gt;select&lt;/code&gt; case will be skipped if the channel is &lt;code&gt;nil&lt;/code&gt;, avoiding the busy-wait loop.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="67---being-puzzled-about-channel-size"&gt;67 - Being puzzled about channel size&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;An unbuffered channel (synchronous channel) has a size of 0: the sender blocks until the receiver receives data from the channel.&lt;/li&gt;
&lt;li&gt;With a buffered channel, the sender will block only when the buffer is full.&lt;/li&gt;
&lt;li&gt;An unbuffered channel enables synchronization between goroutines.&lt;/li&gt;
&lt;li&gt;An unbuffered channel can be used as a notification channel via a channel closure (&lt;code&gt;close&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;When using worker pools, a buffered channel can be used to limit the number of tasks that can be processed concurrently, matching the number of goroutines in the pool.&lt;/li&gt;
&lt;li&gt;Rate-limiting can be achieved with a buffered channel, where the buffer size is the number of tasks that can be processed concurrently.&lt;/li&gt;
&lt;li&gt;In a buffered channel, the lower the value, the bigger the impact on CPU. The higher the value, the bigger the impact on memory.&lt;/li&gt;
&lt;li&gt;Buffered channels can lead to obscure deadlocks.&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s usually best to start with a channel of size &lt;code&gt;1&lt;/code&gt; and perform benchmarks to determine the optimal size.&lt;/li&gt;
&lt;li&gt;Queues tend to be close to full or close to empty anyways (&lt;a href="https://lmax-exchange.github.io/disruptor/files/Disruptor-1.0.pdf"&gt;source&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="68---forgetting-about-possible-side-effects-with-string-formatting"&gt;68 - Forgetting about possible side effects with string formatting&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Printing the values in a context will evaluate the values at the time of the call, which could lead to a data race.&lt;/li&gt;
&lt;li&gt;Acquiring a lock only when it&amp;rsquo;s required is best. This reduces side effects and possible deadlocks. This ties nicely with the fact that a function can go over the &amp;ldquo;error cases&amp;rdquo; before doing anything else that might require more resources (e.g., check the age of a customer is valid before acquiring a lock).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="69---creating-data-races-with-append"&gt;69 - Creating data races with append&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;To prevent data races with &lt;code&gt;append&lt;/code&gt;, one can make a copy of the slice in each goroutine before appending to it.&lt;/li&gt;
&lt;li&gt;If the slice is initialized in such a way that the backing array is shared between goroutines, there will not be a data race since the original slice will remain unchanged.&lt;/li&gt;
&lt;li&gt;Updating a &lt;code&gt;map&lt;/code&gt; from one goroutine will lead to a data race if another goroutine tries to read from the &lt;code&gt;map&lt;/code&gt; at the same time, given that the hashing algorithm for maps introduces some randomness.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="70---using-mutexes-inaccurately-with-slices-and-maps"&gt;70 - Using mutexes inaccurately with slices and maps&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A mutex is used to protect a critical section of code from being accessed by multiple goroutines at the same time.&lt;/li&gt;
&lt;li&gt;A mutex is used to protect a shared resource, like a slice or a map.&lt;/li&gt;
&lt;li&gt;If an operation is lightweight, it is better to use a mutex to protect the resource (protecting the whole function in the process).&lt;/li&gt;
&lt;li&gt;If an operation is heavy, it is better to make a copy of the resource and then update the copy, then replace the original resource with the copy.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="71---misusing-syncwaitgroup"&gt;71 - Misusing &lt;code&gt;sync.WaitGroup&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Add&lt;/code&gt; must be called in the parent goroutine before the child goroutine is started.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Done&lt;/code&gt; must be called in the child goroutine when the child goroutine is finished.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Wait&lt;/code&gt; must be called in the parent goroutine after all child goroutines have been started.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="72---forgetting-about-synccond"&gt;72 - Forgetting about &lt;code&gt;sync.Cond&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sync.Cond&lt;/code&gt; is used to signal a condition to multiple goroutines.&lt;/li&gt;
&lt;li&gt;The updater goroutine locks a mutex, updates the condition, unlocks the mutex, and then signals the condition.&lt;/li&gt;
&lt;li&gt;The listener goroutine(s) lock the mutex, wait for the condition to be signaled, and then unlock the mutex.
&lt;ul&gt;
&lt;li&gt;Lock/unlock is done to offset the fact that &lt;code&gt;Wait&lt;/code&gt; unlocks the mutex and then relocks it when the condition is signaled.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;If no goroutines are waiting for the condition when the call to &lt;code&gt;Broadcast&lt;/code&gt; occurs, the signal is lost.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="73---not-using-errgroup"&gt;73 - Not using &lt;code&gt;errgroup&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pkg.go.dev/golang.org/x/sync/errgroup"&gt;errgroup&lt;/a&gt; provides tools to manage a group of goroutines and propagate errors.&lt;/li&gt;
&lt;li&gt;It can be used to share context between goroutines to allow for cancellation of the group of goroutines upon the first error. If 3 calls are made in parallel and one of them fails fast with an error, the other two will be cancelled instead of waiting.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="74---copying-a-sync-type"&gt;74 - Copying a sync type&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sync.*&lt;/code&gt; types should never be copied.&lt;/li&gt;
&lt;li&gt;If we have a value receiver receiving a struct containing a Mutex, for instance, it will be copied when passed to a function. This will lead to a data race.&lt;/li&gt;
&lt;li&gt;To avoid the issue with a value receiver, either use a pointer receiver or make the mutex field a pointer.&lt;/li&gt;
&lt;li&gt;The same issue will present itself if a function takes a &lt;code&gt;sync.*&lt;/code&gt; type as an argument or if a field in a struct received as a function argument is a &lt;code&gt;sync.*&lt;/code&gt; type.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;go vet&lt;/code&gt; can catch these types of issues.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="the-standard-library"&gt;The standard library&lt;/h2&gt;
&lt;h3 id="75---providing-a-wrong-time-duration"&gt;75 - Providing a wrong time duration&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Always use the &lt;code&gt;time.Duration&lt;/code&gt; API to provide a time duration. Passing any &lt;code&gt;int64&lt;/code&gt; value would work, but the function accepts nanoseconds, which will be confusing.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="76---timeafter-and-memory-leaks"&gt;76 - &lt;code&gt;time.After&lt;/code&gt; and memory leaks&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Resources created by &lt;code&gt;time.After&lt;/code&gt; (e.g., a channel) are released only when the timer expires.&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;time.After&lt;/code&gt; inside a loop will create a new channel each time the loop iterates, leading to a memory leak. Each call to &lt;code&gt;time.After&lt;/code&gt; will consume about 200 bytes. If this is a long-running loop with frequent events, this can be a significant memory leak (e.g., 5 million calls in an hour would be about 1 GB of wasted memory).&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;time.NewTimer&lt;/code&gt; instead, which will create a timer that can be reset, then listen for the timer to expire on the channel (&lt;code&gt;&amp;lt;-timer.C&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="77---common-json-handling-mistakes"&gt;77 - Common JSON-handling mistakes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;When unmarshaling JSON, the &lt;code&gt;json&lt;/code&gt; package will not set the fields of a struct to their zero values if the fields are not present in the JSON.&lt;/li&gt;
&lt;li&gt;Type embedding in a struct can be problematic if the embedded type implements the &lt;code&gt;json.Marshaler&lt;/code&gt; interface, which by virtue of being promoted will be used instead of the &lt;code&gt;json.MarshalJSON&lt;/code&gt; method of the struct.
&lt;ul&gt;
&lt;li&gt;Type embedding makes the parent struct implements interfaces of the embedded type.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;If possible, use a named field instead of an anonymous field (type embedding) to avoid this issue.&lt;/li&gt;
&lt;li&gt;Monotonic clocks guarantee that the time will always increase, even if the system clock is changed. This is useful for measuring time intervals.
&lt;ul&gt;
&lt;li&gt;In Go, monotonic time is part of the same API that provides wall time.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;time.Equal&lt;/code&gt; does not take into account the monotonic clock, so it is possible for two times to be equal even if they are not.&lt;/li&gt;
&lt;li&gt;One can also strip away the monotonic time with &lt;code&gt;time.Truncate(0)&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;To prevent time zone issues, use time.LoadLocation&lt;code&gt;to load the desired time zone or use&lt;/code&gt;time.UTC` to work with UTC time.&lt;/li&gt;
&lt;li&gt;Using a map of any to unmarshal JSON may be convenient but has its drawbacks. For instance, numeric values will be unmarshaled as &lt;code&gt;float64&lt;/code&gt;, even if they contain no decimals.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="78---common-sql-mistakes"&gt;78 - Common SQL mistakes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Forgetting that sql.Open doesn&amp;rsquo;t necessarily establish connections to a database&amp;rdquo;&lt;/em&gt;
&lt;ul&gt;
&lt;li&gt;The behavior depends on the SQL driver.&lt;/li&gt;
&lt;li&gt;To make sure the connection is open, one should call &lt;code&gt;Ping&lt;/code&gt; on the connection or use &lt;code&gt;PingContext&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Forgetting about connections pooling&amp;rdquo;&lt;/em&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SetMaxOpenConns&lt;/code&gt; should be set in any production-grade application to match the load the database can handle.&lt;/li&gt;
&lt;li&gt;If an application generates significant concurrent requests, the default value for &lt;code&gt;SetMaxIdleConns&lt;/code&gt; (2) should be updated.&lt;/li&gt;
&lt;li&gt;If an application can face bursts of requests, one should set &lt;code&gt;SetConnMaxIdleTime&lt;/code&gt; accordingly.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SetConnMaxLifetime&lt;/code&gt; can be useful if an application is behind a load balancer, for example.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Not using prepared statements&amp;rdquo;&lt;/em&gt;
&lt;ul&gt;
&lt;li&gt;Prepared statements should be used in untrusted contexts.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Mishandling null values&amp;rdquo;&lt;/em&gt;
&lt;ul&gt;
&lt;li&gt;If the value of a column can be &lt;code&gt;NULL&lt;/code&gt;, it should be structured as a pointer to receive &lt;code&gt;nil&lt;/code&gt; values.&lt;/li&gt;
&lt;li&gt;Another approach is to use &lt;code&gt;sql.NullString&lt;/code&gt;, &lt;code&gt;sql.NullInt64&lt;/code&gt;, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Not handling row iteration errors&amp;rdquo;&lt;/em&gt;
&lt;ul&gt;
&lt;li&gt;One should check &lt;code&gt;rows.Err&lt;/code&gt; after iterating over rows to check for errors. This is because &lt;code&gt;rows.Next&lt;/code&gt; can stop iterating over rows if an error occurs as well as when all rows have been read.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="79---not-closing-transient-resources"&gt;79 - Not closing transient resources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;HTTP body
&lt;ul&gt;
&lt;li&gt;The response body should be closed to avoid resource leaks (this is done automatically on the server side). This can be done conveniently with &lt;code&gt;defer&lt;/code&gt; function that should call &lt;code&gt;resp.Body.Close()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The body should be closed even if it was not read (e.g., just checking the status code).
&lt;ul&gt;
&lt;li&gt;The body should be read to have a &amp;ldquo;keep-alive&amp;rdquo; connection (&lt;code&gt;io.Discard&lt;/code&gt; is efficient for this purpose).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sql.Rows&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Rows should be closed to release the database connection.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;os.File&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;When closing a file in a &lt;code&gt;defer&lt;/code&gt; function, the error should be sent back to the parent function to be handled in case of error while closing a writable file, for instance.&lt;/li&gt;
&lt;li&gt;If a write should be committed immediately, the file should be flushed before closing it with &lt;code&gt;file.Sync()&lt;/code&gt; (this is a synchronous operation).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="80---forgetting-the-return-statement-after-replying-to-an-http-request"&gt;80 - Forgetting the return statement after replying to an HTTP request&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;http.Error&lt;/code&gt; does not stop the handler&amp;rsquo;s execution.&lt;/li&gt;
&lt;li&gt;After calling &lt;code&gt;http.Error&lt;/code&gt;, the handler should return to stop the execution.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="81---using-the-default-http-client-and-server"&gt;81 - Using the default HTTP client and server&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;HTTP client
&lt;ul&gt;
&lt;li&gt;The default client does not specify any timeouts.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;HTTP server
&lt;ul&gt;
&lt;li&gt;Just like the client, default timeouts should be overridden in production applications to avoid abuse and resource leaks.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="testing"&gt;Testing&lt;/h2&gt;
&lt;h3 id="82---not-categorizing-tests"&gt;82 - Not categorizing tests&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;They should be organized as per the testing pyramid:
&lt;ul&gt;
&lt;li&gt;Unit tests: test a single function or method.&lt;/li&gt;
&lt;li&gt;Integration tests: test the interaction between multiple components.&lt;/li&gt;
&lt;li&gt;End-to-end tests: test the entire system.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;It is not always necessary to run all tests at different stages of a project.&lt;/li&gt;
&lt;li&gt;One can add a build flag such as &lt;code&gt;// go:build integration&lt;/code&gt; to run integration tests only when needed.
&lt;ul&gt;
&lt;li&gt;By default, files without a build flag will be run. Use &lt;code&gt;go test --tags=integration -v .&lt;/code&gt; to run integration tests.&lt;/li&gt;
&lt;li&gt;It is possible to include negation in the build flag, e.g., &lt;code&gt;// go:build !integration&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Build tags apply to the whole file, not individual tests.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Using environment variables may be a better approach than build flags, because one can forget about existing tests that are being ignored and not explicitly flagged as such.
&lt;ul&gt;
&lt;li&gt;Each test can then decide to check for environment variables being set and use &lt;code&gt;t.skip&lt;/code&gt; to skip the test if the environment variable is not set.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;testing.Short()&lt;/code&gt; in a conditional statement to skip long-running tests when running tests with the &lt;code&gt;-short&lt;/code&gt; flag.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="83---not-enabling-the--race-flag"&gt;83 - Not enabling the &lt;code&gt;-race&lt;/code&gt; flag&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;In Go, the race detector is a tool used at runtime to find data races.
&lt;ul&gt;
&lt;li&gt;It is really useful, but brings quite a performance hit (5-10x memory, 2-20x execution time).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Concurrent code should be tested thoroughly to make use of the &lt;code&gt;-race&lt;/code&gt; flag.&lt;/li&gt;
&lt;li&gt;To avoid false positives in data race detections, one can use a loop to run the logic of the test multiple times.&lt;/li&gt;
&lt;li&gt;If needed, &lt;code&gt;// go:build !race&lt;/code&gt; can be used to exclude tests from running with the &lt;code&gt;-race&lt;/code&gt; flag.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="84---not-using-test-execution-modes"&gt;84 - Not using test execution modes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;t.Parallel&lt;/code&gt; can be used to run tests in parallel.
&lt;ul&gt;
&lt;li&gt;Sequential tests run first, then all parallel tests run at the same time (up to &lt;code&gt;GOMAXPROCS&lt;/code&gt;, unless configured otherwise on the command-line).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;There exists a &lt;code&gt;-shuffle&lt;/code&gt; flag to randomize the order of tests, which isolates tests from each other.
&lt;ul&gt;
&lt;li&gt;Using &lt;code&gt;-v&lt;/code&gt; flag along with &lt;code&gt;-shuffle&lt;/code&gt; will print the seed used to shuffle the tests, which can be useful to reproduce results from a CI environment.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="85---not-using-table-driven-tests"&gt;85 - Not using table-driven tests&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;They are shorter and more readable than traditional tests.&lt;/li&gt;
&lt;li&gt;They make refactoring tests easier.&lt;/li&gt;
&lt;li&gt;They make test function names clearer/shorter.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="86---sleeping-in-unit-tests"&gt;86 - Sleeping in unit tests&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Sleeping in a test can mean the test is flaky, especially when testing concurrent code.&lt;/li&gt;
&lt;li&gt;Implementing a retry strategy is better than using a passive &lt;code&gt;time.Sleep&lt;/code&gt; in a test.
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;testify&lt;/code&gt; package provides an &lt;code&gt;Eventually&lt;/code&gt; function that can be used to retry a test function a number of times.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;If a test can be synchronized, using a channel to receive the value from a goroutine to be tested can lead to deterministic results with no need for a sleep. This strategy can also implement a timeout using a &lt;code&gt;select&lt;/code&gt; with a &lt;code&gt;time.After&lt;/code&gt; case.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="87---not-dealing-with-the-time-api-efficiently"&gt;87 - Not dealing with the time API efficiently&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Do not rely on &lt;code&gt;time.Now&lt;/code&gt; in a function. If it must be tested, the time functionality could be injected as a dependency.&lt;/li&gt;
&lt;li&gt;Even better, a function should allow receiving a &lt;code&gt;time.Time&lt;/code&gt; as an argument, so that the caller can provide the needed value.&lt;/li&gt;
&lt;li&gt;Time should also not be stored in a global variable, as all tests will share the same time, preventing them from running in parallel.&lt;/li&gt;
&lt;li&gt;Whenever possible, testing time should be abstracted away from the code being tested.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="88---not-using-testing-utility-packages"&gt;88 - Not using testing utility packages&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;httptest&lt;/code&gt; package
&lt;ul&gt;
&lt;li&gt;It can be used to test HTTP handlers.&lt;/li&gt;
&lt;li&gt;Instead of spinning up Docker, a fake server can be used to test the HTTP client.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;iotest&lt;/code&gt; package
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;io.Reader&lt;/code&gt; and &lt;code&gt;io.Writer&lt;/code&gt; can be tested with this package.&lt;/li&gt;
&lt;li&gt;It can be used to test edge cases, such as reading a file that is too large or to make sure a reader is resilient against errors (e.g., network error).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="89---writing-inaccurate-benchmarks"&gt;89 - Writing inaccurate benchmarks&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;testing.B&lt;/code&gt; to run benchmarks. Then run &lt;code&gt;go test -bench=.&lt;/code&gt;, optionally adding the flag &lt;code&gt;-benchtime=2s&lt;/code&gt; to run the benchmark for 2 seconds (default is 1 second).&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;b.ResetTimer&lt;/code&gt; after an expensive setup to reset the timer.&lt;/li&gt;
&lt;li&gt;If the expensive call must be done for each iteration in the testing loop, then one can use &lt;code&gt;b.StopTimer&lt;/code&gt; and &lt;code&gt;b.StartTimer&lt;/code&gt; to stop and start the timer, respectively.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;perflock&lt;/code&gt; can be used to lock the CPU frequency to avoid performance fluctuations during benchmarking.&lt;/li&gt;
&lt;li&gt;Micro-benchmarks should be avoided, as they can be misleading. &lt;code&gt;benchstat&lt;/code&gt; can be used to compare benchmarks. For instance, run &lt;code&gt;go test -bench=. -count=10 | tee stats.txt&lt;/code&gt; to save the results to a file, then run &lt;code&gt;benchstat stats.txt&lt;/code&gt; to compare the results.&lt;/li&gt;
&lt;li&gt;Reusing the same variable in a benchmark can lead to incorrect results, as the compiler may optimize the code. To avoid this, use &lt;code&gt;b.N&lt;/code&gt; to create a new variable for each iteration.&lt;/li&gt;
&lt;li&gt;Forcing a benchmark to recreate data for each iteration of a loop may prevent CPU caching from affecting the results.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="90---not-exploring-all-the-go-testing-features"&gt;90 - Not exploring all the Go testing features&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;go test -coverprofile=coverage.out ./...&lt;/code&gt; in combination with &lt;code&gt;go tool cover -html=coverage.out&lt;/code&gt; to generate a coverage report.&lt;/li&gt;
&lt;li&gt;Test exposed behavior by creating a separate package so that unexported elements and internals are not tested.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;testing.T&lt;/code&gt; can be used to test the behavior of a function, while &lt;code&gt;testing.B&lt;/code&gt; can be used to test the performance of a function.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;t.Cleanup&lt;/code&gt; can be used to clean up resources after a test (e.g., close a database connection).&lt;/li&gt;
&lt;li&gt;When multiple cleanup functions are needed, they will be executed in reverse order of registration (LIFO), just like using &lt;code&gt;defer&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TestMain&lt;/code&gt; can be used to set up and tear down resources for all tests in a package.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="optimizations"&gt;Optimizations&lt;/h2&gt;
&lt;h3 id="91---not-understanding-cpu-caches"&gt;91 - Not understanding CPU caches&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Mechanical sympathy&lt;/em&gt; is about understanding the hardware to write efficient code.&lt;/li&gt;
&lt;li&gt;Most modern CPU architectures have three levels of cache: L1, L2, and L3.&lt;/li&gt;
&lt;li&gt;L2 is about 4 times slower than L1, and L3 is about 10 times slower than L2.&lt;/li&gt;
&lt;li&gt;L3 is a &lt;em&gt;victim cache&lt;/em&gt; containing only data evicted from L2.&lt;/li&gt;
&lt;li&gt;Access to main memory is between 50 and 100 times slower than access to L1 cache.&lt;/li&gt;
&lt;li&gt;A &lt;em&gt;cache line&lt;/em&gt; is the smallest unit of data that can be transferred between the cache and main memory. When first accessible a memory location, it results in a &lt;em&gt;compulsory miss&lt;/em&gt;. Once the cache line is copied to the cache, subsequent accesses to the same memory location and contiguous ones will result in a &lt;em&gt;cache hit&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;A struct of slices can be about 20% faster than a slice of structs, because of &lt;em&gt;spatial locality&lt;/em&gt;. For 16 elements, it may require 2 cache lines instead of 4 for a struct containing fields with slices.&lt;/li&gt;
&lt;li&gt;Data structures affect &lt;em&gt;predictability&lt;/em&gt; at the CPU level.
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Unit stride&lt;/em&gt; is when the CPU can predict the next memory location to access. All values are contiguous in memory.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Constant stride&lt;/em&gt; is when the CPU can predict the next memory location to access (e.g., read every two elements of a slice), but the values are not contiguous in memory.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Non-unit stride&lt;/em&gt; is when the CPU cannot predict the next memory location to access. The values are not contiguous in memory. This would be the case for a linked list or a slice of pointers.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Cache placement policy&lt;/em&gt; works roughly with the principle of &lt;em&gt;least recently used&lt;/em&gt; (pseudo-LRU). When a cache line is full, the least recently used cache line is evicted.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Conflict misses&lt;/em&gt; happen because the cache is partitioned into sets, and two memory locations map to the same set. This can be avoided by aligning data structures to the cache line size.&lt;/li&gt;
&lt;li&gt;A &lt;em&gt;critical stride&lt;/em&gt; accesses memory addresses with the same set index, leading to a &lt;em&gt;conflict miss&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;This is why micro-benchmarking can be misleading: if the production system uses a different cache architecture, the results may be widely different.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="92---writing-concurrent-code-that-leads-to-false-sharing"&gt;92 - Writing concurrent code that leads to false sharing&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;False sharing&lt;/em&gt; is when two threads access different variables that are on the same cache line, and at least one of the goroutines is a writer.&lt;/li&gt;
&lt;li&gt;This causes the cache line to be invalidated.&lt;/li&gt;
&lt;li&gt;One solution is to add &lt;em&gt;padding&lt;/em&gt; to the struct to ensure that the variables are on different cache lines, so that the same cache line is not copied across different logical cores.&lt;/li&gt;
&lt;li&gt;Another approach is to use channels to communicate between goroutines, which will avoid false sharing, instead of having two goroutines share the same struct.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="93---not-taking-into-account-instruction-level-parallelism"&gt;93 - Not taking into account instruction-level parallelism&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Data hazards&lt;/em&gt; occur when an instruction depends on the result of a previous instruction (e.g., read and increment a variable).&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Control hazards&lt;/em&gt; occur when the CPU cannot predict the next instruction to execute (e.g., conditionals).&lt;/li&gt;
&lt;li&gt;Sometimes, introducing a temporary variable can help the CPU predict the next instruction and parallelize the execution.&lt;/li&gt;
&lt;li&gt;One should remain cautious about micro-optimizations, since the Go compiler keeps evolving, and the generated assembly code may change.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="94---not-being-aware-of-data-alignment"&gt;94 - Not being aware of data alignment&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;quot;[&amp;hellip;] a variable&amp;rsquo;s memory address should be a multiple of its own size&amp;quot;&lt;/em&gt;: this is the principle of &lt;em&gt;data alignment&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;A struct size must be a multiple of the &lt;em&gt;word&lt;/em&gt; size (8 bytes on 64-bit systems).&lt;/li&gt;
&lt;li&gt;A struct being an atomic unit, it should be aligned to the word size, as it won&amp;rsquo;t be reorganized by the compiler. The compiler only adds padding to the fields to guarantee data alignment.&lt;/li&gt;
&lt;li&gt;Rule of thumb: organize structs by size, from largest to smallest, to avoid padding. This leads to better spatial locality and cache usage.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="95---not-understanding-stack-vs-heap"&gt;95 - Not understanding stack vs. heap&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Stack is the default memory. LIFO. Stores all local variables for a goroutine.&lt;/li&gt;
&lt;li&gt;Currently, each goroutine initially gets 2 KB of stack space (contiguous memory, to preserve data locality), which can grow up to 1 GB.&lt;/li&gt;
&lt;li&gt;A stack frame is created for each function call, and it is destroyed when the function returns. The stack frames pile up from accessing different functions inside a given goroutine.&lt;/li&gt;
&lt;li&gt;A stack is self-cleaning: it requires no garbage collection. Each goroutine has its own stack.&lt;/li&gt;
&lt;li&gt;Using pointers, slices, and maps will allocate memory on the heap. One reason is that memory addresses from a stack frame coming from a returned function could not be accessed anymore with a stack.&lt;/li&gt;
&lt;li&gt;The memory heap in Go is managed by the garbage collector and is shared among all goroutines. Variables that &lt;em&gt;could&lt;/em&gt; still be referenced after exiting a function are stored on the heap.&lt;/li&gt;
&lt;li&gt;Allocations on the stack are trivial and faster than on the heap. The stack is also more cache-friendly.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Escape analysis&lt;/em&gt; is a compiler optimization that determines whether a variable can be allocated on the stack or the heap.
&lt;ul&gt;
&lt;li&gt;&amp;ldquo;Sharing up&amp;rdquo; escapes to the heap (need a reference from a context that is about to be destroyed).&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Sharing down&amp;rdquo; stays on the stack (no reference to the variable outside the function).&lt;/li&gt;
&lt;li&gt;Global variables are stored on the heap, because they can be shared across goroutines.&lt;/li&gt;
&lt;li&gt;A pointer sent to a channel will escape to the heap.&lt;/li&gt;
&lt;li&gt;A variable referenced by a value sent to a channel.&lt;/li&gt;
&lt;li&gt;When a local variable is too large to fit on the stack.&lt;/li&gt;
&lt;li&gt;When building a program, the &lt;code&gt;-gcflags &amp;quot;-m&amp;quot;&lt;/code&gt; flag can be used to see the escape analysis results.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="96---not-knowing-how-to-reduce-allocations"&gt;96 - Not knowing how to reduce allocations&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;Reader&lt;/code&gt; interface takes in a slice of bytes and returns the number of bytes read and an error. If it had been the other way around, the slice would always escape to the heap.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sync.Pool&lt;/code&gt; can be used to reduce the number of allocations. It is a pool of objects that can be reused. E.g., when writing a slice of bytes to a buffer, the buffer can be reused.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sync.Pool&lt;/code&gt; is thread-safe.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="97---not-relying-on-inlining"&gt;97 - Not relying on inlining&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Mid-stack inlining&lt;/em&gt; is when the compiler inlines a function call that is in the middle of the stack, not at the top (e.g., &lt;code&gt;main&lt;/code&gt; calls &lt;code&gt;foo&lt;/code&gt;, which calls &lt;code&gt;bar&lt;/code&gt;, both functions are simple, so &lt;code&gt;bar&lt;/code&gt; is inlined in &lt;code&gt;foo&lt;/code&gt; and &lt;code&gt;foo&lt;/code&gt; is in turn inlined in &lt;code&gt;main&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Slow vs. fast paths can be optimized by the compiler. The slow path can be extracted to a separate function, which will be inlined in the fast path (&lt;em&gt;&amp;ldquo;fast-path inlining technique&amp;rdquo;&lt;/em&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="98---not-using-go-diagnostics-tooling"&gt;98 - Not using Go diagnostics tooling&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Profiling
&lt;ul&gt;
&lt;li&gt;With profiling, one can see where the program is spending most of its time. &lt;code&gt;pprof&lt;/code&gt; is the Go profiler.&lt;/li&gt;
&lt;li&gt;There are different profiles: CPU (time spent), goroutine (stack traces of ongoing routines), heap (check memory leaks and current allocations), mutex (check time spent in locking calls), block (see where goroutines waits on synchronous operations).&lt;/li&gt;
&lt;li&gt;A profile can be generated while benchmarking: &lt;code&gt;go test -bench=. -cpuprofile profile.out&lt;/code&gt;. This profile can be used with &lt;code&gt;go tool pprof -http=:8080 profile.out&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Heap profiling
&lt;ul&gt;
&lt;li&gt;It&amp;rsquo;s a good idea to force garbage collection before dumping the heap profile to avoid false assumptions. This can be done via the web UI at &lt;code&gt;http://localhost/debug/pprof/heap?gc=1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Snapshots can be compared with &lt;code&gt;go tool pprof -http=:8080 -diff_base &amp;lt;file2&amp;gt; &amp;lt;file1&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Goroutine profiling
&lt;ul&gt;
&lt;li&gt;We can inspect to check for &amp;ldquo;goroutine leaks&amp;rdquo;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Block profiling
&lt;ul&gt;
&lt;li&gt;Useful to check performance affected by blocking calls. Must call &lt;code&gt;runtime.SetBlockProfileRate&lt;/code&gt; to enable it. This profiler stays on until the program ends.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Mutex profiling
&lt;ul&gt;
&lt;li&gt;Useful to check performance affected by mutex locks. Must call &lt;code&gt;runtime.SetMutexProfileFraction&lt;/code&gt; to enable it. This profiler stays on until the program ends.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;A single profiler should be enabled at a time.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Execution tracer
&lt;ul&gt;
&lt;li&gt;Used to check how garbage collection performs, how goroutines are scheduled, and how the scheduler behaves.&lt;/li&gt;
&lt;li&gt;It can be enabled with &lt;code&gt;go test -bench=. -v -trace=trace.out&lt;/code&gt; or by downloading a trace file with &lt;code&gt;/debug/pprof/trace?debug=0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The granularity of the tracer is per goroutine (unlike CPU profiling, which is per function).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="99---not-understanding-how-the-gc-works"&gt;99 - Not understanding how the GC works&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;debug.FreeOSMemory()&lt;/code&gt; can be used to force the garbage collector to release memory to the OS.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GODEBUG=gctrace=1&lt;/code&gt; can be used to see the garbage collector in action.&lt;/li&gt;
&lt;li&gt;One can force a large allocation of memory with something like &lt;code&gt;var min = make([]byte, 1_000_000_000)&lt;/code&gt; so that the garbage collector will only trigger once the heap reaches twice the size of the allocation, which can reduce the impact on latency if otherwise many garbage collections would be triggered in a short period of time. On most systems, this leads to a &lt;em&gt;lazy allocation&lt;/em&gt; (in the virtual address space). This is useful when the heap peak is known in advance.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="100---not-understanding-the-impacts-of-running-go-in-docker-and-kubernetes"&gt;100 - Not understanding the impacts of running Go in Docker and Kubernetes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;CPU throttling can occur in Docker and Kubernetes, leading to performance degradation.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GOMAXPROCS&lt;/code&gt; should be set to the number of CPUs available to the container.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="resources-and-references"&gt;Resources and references&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cs.opensource.google/go/x/website/&amp;#43;/e0d934b4:blog/blog.go;l=32"&gt;&lt;code&gt;init&lt;/code&gt; function of the Go blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.manning.com/books/100-go-mistakes-and-how-to-avoid-them"&gt;100 Go Mistakes and How to Avoid Them&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/teivah/advent-of-code"&gt;Advent of Code solutions by teivah&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/teivah/100-go-mistakes"&gt;GitHub repository of 100 Go Mistakes by teivah&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pubmed.ncbi.nlm.nih.gov/27648988/"&gt;Learning from Errors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pubmed.ncbi.nlm.nih.gov/22042726/"&gt;Mind your errors: evidence for a neural mechanism linking growth mind-set to adaptive posterror adjustments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/golang-standards/project-layout"&gt;Project layout&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.synopsys.com/2021-01-06-Synopsys-Sponsored-CISQ-Research-Estimates-Cost-of-Poor-Software-Quality-in-the-US-2-08-Trillion-in-2020"&gt;Synopsys-Sponsored CISQ Research Estimates Cost of Poor Software Quality in the US $2.08 Trillion in 2020&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://teivah.dev/"&gt;teivah.dev&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://engineering.purdue.edu/WukLab/GoStudy-ASPLOS19.pdf"&gt;Understanding Real-World Concurrency Bugs in Go&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Sébastien Lavoie</dc:creator><pubDate>Sat, 24 Aug 2024 14:55:00 -0600</pubDate><guid isPermaLink="false">https://www.sglavoie.com/posts/book-summary-100-go-mistakes-and-how-to-avoid-them/</guid><category>learnings</category><category>best practices</category><category>books</category><category>go</category><category>skimming-notes</category></item><item><title>Book summary: Refactoring UI</title><link>https://www.sglavoie.com/posts/book-summary-refactoring-ui/</link><description>Refactoring UI provides guidance for non-designers on how to create polished, professional visual interfaces without formal graphic design skills.</description><content:encoded>&lt;hr&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;It covers core principles like establishing consistent systems for recurring elements such as spacing and typography.&lt;/li&gt;
&lt;li&gt;It advises generous use of white space and thoughtful visual hierarchy.&lt;/li&gt;
&lt;li&gt;Steps for adding depth through lighting, shadows, and layers are outlined.&lt;/li&gt;
&lt;li&gt;Recommendations are provided for working with images and illustrations effectively.&lt;/li&gt;
&lt;li&gt;Tips on elevating default elements and breaking conventions to avoid typical, cookie-cutter designs are included.&lt;/li&gt;
&lt;li&gt;Studying other designers&amp;rsquo; work and unintuitive decisions is encouraged to expand one&amp;rsquo;s skills.&lt;/li&gt;
&lt;li&gt;The goal is to equip readers with knowledge to thoughtfully craft interfaces that appear purposefully designed without simply mimicking common graphical tropes.&lt;/li&gt;
&lt;li&gt;By learning fundamental visual design concepts tailored to user interfaces, anyone can create clean, functional, and visually engaging digital experiences.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="starting-from-scratch"&gt;Starting from scratch&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Start with a feature, not a layout&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Design the shell and navigation later once you know what features you need.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Detail comes later&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Design with a big sharpie.&lt;/li&gt;
&lt;li&gt;Design in grayscale to use spacing, contrast and size to do the heavy lifting.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don&amp;rsquo;t over-invest&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Go with low-fidelity sketches and wireframes first to move fast. Don&amp;rsquo;t get bogged down with details like fonts and colors early on.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don&amp;rsquo;t design too much&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Work in cycles&lt;/strong&gt;: design a feature simply, build it, fix errors.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Be a pessimist&lt;/strong&gt;: expect it to be hard to build, and postpone building it (e.g., commenting system without attachments at first).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="choose-a-personality"&gt;Choose a personality&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Font choice
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Elegant/classic&lt;/em&gt;: serif&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Playful&lt;/em&gt;: Sans-serif&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Plain&lt;/em&gt;: neutral sans-serif&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Color
&lt;ul&gt;
&lt;li&gt;Blue: safe and familiar, good overall pick.&lt;/li&gt;
&lt;li&gt;Gold: expensive, sophisticated.&lt;/li&gt;
&lt;li&gt;Pink: more fun, less serious.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Border radius
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Small&lt;/em&gt; &amp;ndash; neutral, not much personality.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Large&lt;/em&gt; &amp;ndash; more playful.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;None&lt;/em&gt; &amp;ndash; more serious/formal.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stay consistent&lt;/strong&gt; &amp;ndash; either rounded or not, same everywhere.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Language
&lt;ul&gt;
&lt;li&gt;Choose the right words for the desired tone.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Decide what you actually want
&lt;ul&gt;
&lt;li&gt;Look at the websites your expected customers use? Serious, then that&amp;rsquo;s the feel. Playful? Then that.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="limit-your-choices"&gt;Limit your choices&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Define systems in advance&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Have a working palette color and limit yourself to it.&lt;/li&gt;
&lt;li&gt;Use a type scale to avoid picking specific font sizes.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Designing by process of elimination.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Systematize&lt;/strong&gt; as much as possible to avoid repetitive low-level decisions.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="hierarchy"&gt;Hierarchy&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Visual hierarchy refers to how elements appear in importance relative to one another.&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t let HTML semantics dictate visual styling &amp;ndash; style based on visual hierarchy.&lt;/li&gt;
&lt;li&gt;Size isn&amp;rsquo;t everything. Bold font communicates importance. &lt;em&gt;De-emphasize&lt;/em&gt; secondary/tertiary information to highlight the most important elements.&lt;/li&gt;
&lt;li&gt;Rely on &lt;em&gt;font weight&lt;/em&gt; and &lt;em&gt;color&lt;/em&gt;, not just font size, to create hierarchy.&lt;/li&gt;
&lt;li&gt;Limit yourself to &lt;em&gt;2-3 colors&lt;/em&gt; and font weights to define hierarchy.&lt;/li&gt;
&lt;li&gt;On colored backgrounds, pick a color with similar hue but lower saturation/lightness rather than just making text gray.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Avoid label&lt;/strong&gt;: value formats &amp;ndash; use formats, context, and combined labels/values for clarity without labels.&lt;/li&gt;
&lt;li&gt;Treat labels as secondary content when needed &amp;ndash; smaller, lower contrast, lighter weight.&lt;/li&gt;
&lt;li&gt;De-emphasize competing elements instead of just emphasizing the key element.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Combine labels and values&lt;/em&gt;: e.g., instead of &amp;ldquo;&lt;em&gt;In stock: 12&lt;/em&gt;&amp;rdquo;, use &amp;ldquo;&lt;em&gt;12 left in stock&lt;/em&gt;&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;Treat labels as supporting content.&lt;/li&gt;
&lt;li&gt;Design buttons based on hierarchy, not just semantics. Primary/destructive actions don&amp;rsquo;t always need prominence.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="layout-and-spacing"&gt;Layout and spacing&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Start with too much white space&lt;/strong&gt;, then remove until happy &amp;ndash; often ends up being &amp;ldquo;just enough&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;Compact layouts have their place but should be a deliberate choice, not the default.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Establish a spacing and sizing system&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;A linear scale won&amp;rsquo;t work&lt;/li&gt;
&lt;li&gt;Create a constrained spacing/sizing system based on a base unit (like &lt;code&gt;16px&lt;/code&gt;) to work faster with more consistency.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Don&amp;rsquo;t feel the need to fill the whole screen&lt;/em&gt; &amp;ndash; give elements only the space they need.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Grids are overrated&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Not all elements should be fluid&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t shrink an element until you need to.&lt;/li&gt;
&lt;li&gt;Grids can bring order but don&amp;rsquo;t have to dictate every layout decision.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Balance layouts in columns&lt;/em&gt; instead of just making things wider.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Relative sizing doesn&amp;rsquo;t scale&lt;/em&gt;.
&lt;ul&gt;
&lt;li&gt;Fix element widths when flexibility isn&amp;rsquo;t needed.&lt;/li&gt;
&lt;li&gt;Scale elements independently &amp;ndash; don&amp;rsquo;t rely on relative units like &lt;code&gt;em&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Avoid ambiguous spacing&lt;/strong&gt;: increase spacing between groups and reduce spacing within groups to show relationships.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="text"&gt;Text&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Establish a constrained type scale&lt;/strong&gt; to speed up font size decisions and add consistency. Avoid strict modular scales.&lt;/li&gt;
&lt;li&gt;Use &lt;em&gt;pixel&lt;/em&gt; or &lt;code&gt;rem&lt;/code&gt; units &amp;ndash; not &lt;code&gt;em&lt;/code&gt; &amp;ndash; to guarantee sizes match the scale.&lt;/li&gt;
&lt;li&gt;For UI design, &lt;em&gt;pick scale values by hand&lt;/em&gt; instead of using a mathematical ratio.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use high quality fonts&lt;/strong&gt; &amp;ndash; safe bets are neutral sans-serifs and system fonts.&lt;/li&gt;
&lt;li&gt;Use the system stack if not sure: &lt;code&gt;-apple-system, Segoe UI, Roboto, Noto Sans, Ubuntu, Cantarell, Helvetica Neue;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Favor fonts with &lt;em&gt;5+ weights&lt;/em&gt; and optimized for legibility.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keep line length between 45-75 characters&lt;/strong&gt; (20-35 &lt;code&gt;em&lt;/code&gt;) for optimal readability.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Align mixed font sizes by their baselines&lt;/strong&gt;, not centers.&lt;/li&gt;
&lt;li&gt;Use taller line-heights for small text and shorter line-heights for large text.&lt;/li&gt;
&lt;li&gt;Subtly emphasize links in UIs, no need for high contrast colors.&lt;/li&gt;
&lt;li&gt;Left align most text, right align numbers in tables. Justify only with hyphenation.&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t center long form text.&lt;/li&gt;
&lt;li&gt;Tighten letter-spacing for headlines set in text faces. Loosen letter-spacing for all-caps.&lt;/li&gt;
&lt;li&gt;Not all links need to be emphasized boldly, especially in UIs where there is plenty of interactivity.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="colors"&gt;Colors&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HSL&lt;/strong&gt; (&lt;em&gt;hue&lt;/em&gt;, &lt;em&gt;saturation&lt;/em&gt;, &lt;em&gt;lightness&lt;/em&gt;) is more intuitive to the human eye: use it instead of &lt;strong&gt;HEX&lt;/strong&gt; and &lt;strong&gt;RGB&lt;/strong&gt;.
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Hue&lt;/em&gt; is the color position on the color wheel. 0° is red, 120° is green, and 240° is blue.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Saturation&lt;/em&gt; represents the vividness. 0% is grey while 100% is vibrant and intense.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Lightness&lt;/em&gt; measures how close a closer is to black (0%) or white (100%), where 50% is the pure color.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HSL&lt;/strong&gt; is not the same as &lt;strong&gt;HSB&lt;/strong&gt; (&lt;code&gt;lightness != brightness&lt;/code&gt;). In &lt;strong&gt;HSB&lt;/strong&gt;, 0% brightness is always black but 100% brightness is white when saturation is set to 0%.
&lt;ul&gt;
&lt;li&gt;With 100% saturation in both HSL and HSB, 100% brightness is equivalent to 50% lightness (pure color).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Don&amp;rsquo;t rely solely on color to convey information&lt;/em&gt; - support it with other indicators.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="a-good-color-palette"&gt;A good color palette&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;You need more colors than you think - aim for &lt;em&gt;10+ shades&lt;/em&gt; of &lt;em&gt;greys&lt;/em&gt;, &lt;em&gt;primary colors&lt;/em&gt;, and &lt;em&gt;accent colors&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Grey&lt;/em&gt;
&lt;ul&gt;
&lt;li&gt;There is a lot of grey in UIs: between &lt;em&gt;8-10 shades of grey&lt;/em&gt; is a good array of options to pick from. Use very dark grey instead of true black so it looks more natural.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Primary colors&lt;/em&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;1 or 2 is great, with 5-10 different shades&lt;/em&gt; (e.g., Facebook is basically blue). A very light shade is good for a tinted background (e.g., alerts), darker shades work great for text.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Accent colors&lt;/em&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Use sparingly&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Needed to bring &lt;em&gt;semantic meaning&lt;/em&gt;, such as red for danger, yellow for warnings and green for positive trends.&lt;/li&gt;
&lt;li&gt;For a complex UI, can be as many as 10 different colors, each with 5-10 shades.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="building-a-color-palette"&gt;Building a color palette&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Define color shades upfront instead of generating on the fly. Pick base, darkest, lightest, then fill in gaps.&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t rely purely on math to generate shades - trust your eyes and tweak if needed.&lt;/li&gt;
&lt;li&gt;Choose the base color first. This is the color in the middle that light and dark shades are derived from.&lt;/li&gt;
&lt;li&gt;Center: pick a shade that would work well as a button background.&lt;/li&gt;
&lt;li&gt;Extremes: pick the lightness color to work as a background and the darkest to work on top as text to get enough contrast.&lt;/li&gt;
&lt;li&gt;Repeat the process to pick the remaining colors in between. &lt;em&gt;9 shades is a good sweet spot as it divides cleanly&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;For grays, the base color doesn&amp;rsquo;t matter nearly as much: same process.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="perceived-brightness"&gt;Perceived brightness&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;In the HSL system, the further away a color is from 50% lightness, the more saturated it should be so it doesn&amp;rsquo;t look washed out.&lt;/li&gt;
&lt;li&gt;The perceived brightness of a color to the human eye can be calculated as follows:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;brightness = √(0.299·r² + 0.587·g² + 0.114·b²) / 255&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Green is perceived as more bright than red, which in turn is perceived as more bright than blue. With this data point, we can tweak the perceived brightness of a color by rotating the color wheel towards the desired perceived brightness.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Grays aren&amp;rsquo;t usually totally desaturated&lt;/em&gt;.
&lt;ul&gt;
&lt;li&gt;To make them feel &amp;ldquo;&lt;em&gt;cool&lt;/em&gt;&amp;rdquo; saturate them with some blue.&lt;/li&gt;
&lt;li&gt;To make them feel &amp;ldquo;&lt;em&gt;warm&lt;/em&gt;&amp;rdquo;, saturate them with yellow or orange.&lt;/li&gt;
&lt;li&gt;The same &amp;ldquo;&lt;em&gt;saturation curve&lt;/em&gt;&amp;rdquo; applies to grays: the further away from 50% brightness, the more they should be saturated to avoid looking washed out.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="a-word-on-accessibility"&gt;A word on accessibility&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Instead of having elements like dark-colored pills with a white foreground color, flip the contrast by having light-colored pills with a dark version of the color in the foreground so they won&amp;rsquo;t grab too much attention.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rotate the hue to make the color pop more&lt;/strong&gt;. For instance, instead of cranking up the lightness to meet ideal contrast ratios, rotate the hue and possibly keep the saturation high for a vibrant contrast.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don&amp;rsquo;t rely on color alone&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Add visual cues (like arrows for upward/downward trends). Designing in grayscale first will help spot these kinds of issues.&lt;/li&gt;
&lt;li&gt;For things like a graph, playing with contrast will produce clearer results.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="depth"&gt;Depth&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Mimic how light works in the real world.
&lt;ul&gt;
&lt;li&gt;Emulate a light source coming from above to make elements appear raised or inset.&lt;/li&gt;
&lt;li&gt;For instance, with a button, if light comes from above, then the top should be lighter (e.g., &lt;code&gt;box-shadow inset&lt;/code&gt;) than the bottom (&lt;code&gt;box-shadow&lt;/code&gt;, casting shadow only below).&lt;/li&gt;
&lt;li&gt;For elements that are &amp;ldquo;inset&amp;rdquo; (I.e., more depth in the center), the opposite effect should happen: more light reaches the bottom part (vertical &lt;code&gt;box-shadow&lt;/code&gt;, going up) than the top part (&lt;code&gt;box-shadow inset&lt;/code&gt; such that it casts some shadow towards the bottom).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Don&amp;rsquo;t overdo it&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="shadows"&gt;Shadows&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Use shadows to convey elevation&lt;/em&gt;
&lt;ul&gt;
&lt;li&gt;When something appears to be raised from the background (e.g., bigger &lt;code&gt;drop-shadow&lt;/code&gt;), &lt;em&gt;it attracts the user&amp;rsquo;s focus&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Small shadows&lt;/em&gt; are useful to attract attention to the primary action to be performed.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Medium shadows&lt;/em&gt; are useful for things like a dropdown so it looks separate from the background.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Large shadows&lt;/em&gt; are great for modals.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;5 options&lt;/em&gt; is usually plenty.&lt;/li&gt;
&lt;li&gt;Can be used for interactivity, like when dragging a row from a table.&lt;/li&gt;
&lt;li&gt;A button can feel like it&amp;rsquo;s being &amp;ldquo;pressed&amp;rdquo; by reducing its shadow on click (i.e., it gets closer to the page or farther away from the user).&lt;/li&gt;
&lt;li&gt;Casting shadows
&lt;ul&gt;
&lt;li&gt;To simulate a shadow cast by &lt;em&gt;direct light&lt;/em&gt; (e.g., a long shadow behind a plant getting some sunlight), use a larger, more diffuse and subtle shadow.&lt;/li&gt;
&lt;li&gt;To simulate a shadow cast by &lt;em&gt;ambient light&lt;/em&gt; (e.g., the shadow right below the plant&amp;rsquo;s pot where light can&amp;rsquo;t hardly reach), use a narrow and darker shadow.&lt;/li&gt;
&lt;li&gt;Apply both types of shadow casting at once to create visually compelling effects.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Define a fixed set of shadows as an elevation system for consistency.&lt;/li&gt;
&lt;li&gt;Even flat designs can have depth
&lt;ul&gt;
&lt;li&gt;Flat design does not convey depth with shadows and gradients.&lt;/li&gt;
&lt;li&gt;Create depth with color: lighter colors feel closer.&lt;/li&gt;
&lt;li&gt;Can use short, offset shadows with no blur at all.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Overlap elements to create layers
&lt;ul&gt;
&lt;li&gt;E.g., position a block with a negative margin so it appears on top of two different sections.&lt;/li&gt;
&lt;li&gt;For smaller elements like avatars, an invisible border around it can make it pop better.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="images-and-contrast"&gt;Images and contrast&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Images
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Use high quality photos&lt;/em&gt; - hire a pro or use stock sites for generics. Don&amp;rsquo;t expect good results from phone pics.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Text needs consistent contrast&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Add an overlay to background images to get readable headlines.&lt;/li&gt;
&lt;li&gt;Lower the image contrast. Adjust the brightness to compensate.&lt;/li&gt;
&lt;li&gt;Colorize the image with a single color.&lt;/li&gt;
&lt;li&gt;Use text shadows instead of overlays to preserve image dynamics.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="scale"&gt;Scale&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Don&amp;rsquo;t scale up icons&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Even for SVGs, they will lack detail if they were designed correctly at a smaller scale: use a more detailed icon instead.&lt;/li&gt;
&lt;li&gt;If updating the icon is not an option, enclose it inside another shape (e.g., a circle with a background color) to occupy more space.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don&amp;rsquo;t scale down screenshots&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Details will be distorted and hard to read.&lt;/li&gt;
&lt;li&gt;Either use a presentation from a smaller device (e.g., the tablet version on a desktop) or take a partial screenshot (e.g., &amp;ldquo;zoom in&amp;rdquo; effect with a circle or simply crop the original image).&lt;/li&gt;
&lt;li&gt;If a more complex UI needs to be presented, simplify the original image so the user doesn&amp;rsquo;t try to read a 4px font size but instead sees simpler shapes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don&amp;rsquo;t scale down icons&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;E.g. for a favicon, redraw a simplified version instead: details will look fuzzy at a small size.&lt;/li&gt;
&lt;li&gt;User-submitted content (e.g., Instagram feed)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Control the size and shape&lt;/em&gt;: crop images to keep the aspect ratio of containers so they don&amp;rsquo;t disturb layout (e.g., background-size: cover).&lt;/li&gt;
&lt;li&gt;Add inner shadows or borders to user images to prevent background bleed.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="finishing-touches"&gt;Finishing touches&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Use icons instead of bullets in a bulleted list.&lt;/li&gt;
&lt;li&gt;Play with size and colors. E.g., you can have huge quotes around the block of text representing a testimonial.&lt;/li&gt;
&lt;li&gt;Links deserve special styling.&lt;/li&gt;
&lt;li&gt;Use custom checkboxes and radio buttons in forms: integrate the brand&amp;rsquo;s primary color(s).&lt;/li&gt;
&lt;li&gt;Add color with accent borders, e.g. across the top of a card element, when highlighting active elements in the UI or to the left of alert messages.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Decorate backgrounds&lt;/em&gt;
&lt;ul&gt;
&lt;li&gt;Change the background color. Works great for different page sections.&lt;/li&gt;
&lt;li&gt;Can use gradients: use two hues that are within 30˚ of each other.&lt;/li&gt;
&lt;li&gt;Use repeating patterns.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consider the empty state carefully&lt;/strong&gt;, especially when the user is meant to fill the UI with data.
&lt;ul&gt;
&lt;li&gt;Prioritize empty states with illustrations and clear calls to action.&lt;/li&gt;
&lt;li&gt;I.e., a contact page with no contacts should feel welcoming, with some icon and button to add a contact instead of a blank page with an error-like statement.&lt;/li&gt;
&lt;li&gt;Similarly, things like dashboard may present a bunch of options that won&amp;rsquo;t apply until there is something to show: simplify the user experience.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use less borders&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Use box shadows, spacing, and color instead of borders to distinguish elements.&lt;/li&gt;
&lt;li&gt;Have two background colors for containers (e.g., darker footer that looks separate from the main content).&lt;/li&gt;
&lt;li&gt;Replace borders with additional spacing.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Make components more exciting&lt;/em&gt;
&lt;ul&gt;
&lt;li&gt;E.g., a table may not need all its columns if a particular column does not need to be sorted and could instead be part of another one (e.g., combining a dollar amount with a plan type or a person&amp;rsquo;s name with its title).&lt;/li&gt;
&lt;li&gt;Radio buttons could be cards instead if they are an important part of the UI.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="next-level"&gt;Next level&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Look for unintuitive decisions in designs you admire to find new ideas.&lt;/li&gt;
&lt;li&gt;Rebuild interfaces you love to discover small details that make them exceptional.&lt;/li&gt;
&lt;li&gt;Study others to build a solid foundation.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The concepts covered in this book demonstrate that effective visual design does not require innate artistic talent or formal training. By approaching interfaces methodically, establishing constraints through systems for recurring elements, and learning to strategically apply principles like visual hierarchy, white space, and depth cues, anyone can create designs that appear polished and purposeful. While internalizing these fundamentals does take practice, the ideas presented remove much of the subjective, nuanced expertise needed for things like picking complementary colors or balancing composition in illustrations.&lt;/p&gt;
&lt;p&gt;By focusing on interfaces rather than free-form graphical work, the required skills become more structured and intentional choices drive outcomes. While studying formally can provide more breadth, the core principles in this text are enough to equip non-designers with the ability to build clean, intuitive, and aesthetically pleasing digital products. By implementing the recommendations covered, engineers and others new to design can gain confidence in their interface design skills. A great, concise read!&lt;/p&gt;</content:encoded><dc:creator>Sébastien Lavoie</dc:creator><pubDate>Sat, 09 Sep 2023 17:56:00 -0600</pubDate><guid isPermaLink="false">https://www.sglavoie.com/posts/book-summary-refactoring-ui/</guid><category>learnings</category><category>best practices</category><category>books</category><category>design</category><category>refactoring</category><category>skimming-notes</category><category>ui</category></item><item><title>Book summary: The Effective Engineer</title><link>https://www.sglavoie.com/posts/book-summary-the-effective-engineer/</link><description>After reading Letters to a New Developer, one of the next logical steps on this learning path was to read [The Effective Engineer][effective-website]. I hope you enjoy this simple yet effective book summary!</description><content:encoded>&lt;hr&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;This wonderful, mostly non-technical book is a compilation of the best practices and lessons learned by the author, &lt;a href="https://edmondlau.co/"&gt;Edmond Lau&lt;/a&gt;, during his impressive career as a software engineer. It is a must-read for anyone who wants to improve their skills and become more effective at what they do. The following are short excerpts from the condensed knowledge found in the book, sprinkled with some minor comments of my own along the way. These are extracted from the book’s takeaway sections and headings: I wholeheartedly recommend &lt;a href="https://www.effectiveengineer.com/"&gt;reading the work&lt;/a&gt; in its entirety for an immersive exposure to the fantastic storytelling capabilities of Edmond!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="book-summary"&gt;Book summary&lt;/h2&gt;
&lt;h2 id="part-1-adopt-the-right-mindset"&gt;Part 1: Adopt the Right Mindset&lt;/h2&gt;
&lt;h3 id="focus-on-high-leverage-activities"&gt;Focus on high-leverage activities&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Leverage = Impact produced / Time invested&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Leverage is the ROI for the effort put in.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Meta-skills&lt;/strong&gt; are important to know where to focus one’s time and energy to translate more of one’s efforts into impact.&lt;/li&gt;
&lt;li&gt;Leverage your time by always finding ways to perform activities quicker and making sure those activities will have a &lt;strong&gt;meaningful impact&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="optimize-for-learning"&gt;Optimize for learning&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Make your learning rate a priority&lt;/strong&gt;: it compounds like interest over time.&lt;/li&gt;
&lt;li&gt;Study code for core abstractions written by the best engineers at your company.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Write more code&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Go through any technical, educational material available internally.&lt;/li&gt;
&lt;li&gt;Master the programming languages that you use.&lt;/li&gt;
&lt;li&gt;Send your code reviews to the harshest critics.&lt;/li&gt;
&lt;li&gt;Enroll in classes on areas where you want to improve.&lt;/li&gt;
&lt;li&gt;Participate in design discussions of projects you’re interested in.&lt;/li&gt;
&lt;li&gt;Work on a diversity of projects.&lt;/li&gt;
&lt;li&gt;Make sure you’re on a team with at least a few senior engineers whom you can learn from.&lt;/li&gt;
&lt;li&gt;Jump fearlessly into code you don’t know.&lt;/li&gt;
&lt;li&gt;Learn new programming languages and frameworks.&lt;/li&gt;
&lt;li&gt;Invest in skills that are in high demand.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Read books&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Join a discussion group.&lt;/li&gt;
&lt;li&gt;Attend talks, conferences, and meetups.&lt;/li&gt;
&lt;li&gt;Build and maintain a strong network of relationships.&lt;/li&gt;
&lt;li&gt;Follow bloggers who teach.&lt;/li&gt;
&lt;li&gt;Write to teach.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tinker on side projects&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pursue what you love&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="prioritize-regularly"&gt;Prioritize regularly&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Track to-dos in a single list.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Focus on what produces value directly&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Focus on the &lt;em&gt;important and non-urgent&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Limit the amount of work in progress.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use implementation intentions&lt;/strong&gt; (if-then planning).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prioritize&lt;/strong&gt; routinely.&lt;/li&gt;
&lt;li&gt;Write down and &lt;em&gt;review&lt;/em&gt; to-dos.&lt;/li&gt;
&lt;li&gt;Reduce &lt;em&gt;context switches&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="part-2-execute-execute-execute"&gt;Part 2: Execute, Execute, Execute&lt;/h2&gt;
&lt;h3 id="invest-in-iteration-speed"&gt;Invest in iteration speed&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Move fast to learn fast.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Invest in time-saving tools&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Shorten your debugging and validation loops.&lt;/li&gt;
&lt;li&gt;Master your programming environment.&lt;/li&gt;
&lt;li&gt;Get proficient with your favorite text editor or IDE.&lt;/li&gt;
&lt;li&gt;Learn at least one productive, high-level programming language (&lt;em&gt;scripting&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;Get familiar with UNIX (or Windows) &lt;em&gt;shell commands&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Prefer the keyboard&lt;/em&gt; over the mouse.&lt;/li&gt;
&lt;li&gt;Automate your manual workflows.&lt;/li&gt;
&lt;li&gt;Test out ideas on an interactive interpreter.&lt;/li&gt;
&lt;li&gt;Make it fast and easy to run just the unit tests associated with your current changes.&lt;/li&gt;
&lt;li&gt;Don’t ignore non-engineering bottlenecks.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="measure-what-you-want-to-improve"&gt;Measure what you want to improve&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Use metrics to drive progress.&lt;/li&gt;
&lt;li&gt;Pick the right metric to incentivize the behavior you want.&lt;/li&gt;
&lt;li&gt;Instrument everything to understand what is going on.&lt;/li&gt;
&lt;li&gt;Internalize useful numbers (e.g., number of active users, requests per second, amount of data accessed and written daily, etc.).&lt;/li&gt;
&lt;li&gt;Be skeptical about data integrity.&lt;/li&gt;
&lt;li&gt;Log data liberally, in case it turns out to be useful later on.&lt;/li&gt;
&lt;li&gt;Build tools to iterate on data accuracy sooner.&lt;/li&gt;
&lt;li&gt;Write end-to-end integration tests to validate your entire analytics pipeline.&lt;/li&gt;
&lt;li&gt;Examine collected data sooner.&lt;/li&gt;
&lt;li&gt;Cross-validate data accuracy by computing the same metric in multiple ways.&lt;/li&gt;
&lt;li&gt;When a number does look off, dig into it early.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="validate-your-ideas-early-and-often"&gt;Validate your ideas early and often&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Find low-effort ways to validate your work.
&lt;ul&gt;
&lt;li&gt;Approach a problem iteratively to reduce wasted effort.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Continuously validate product changes with A/B testing.
&lt;ul&gt;
&lt;li&gt;Reduce the risk of large implementations by using small validations.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Beware the one-person team (you need feedback early to make sure you’re working on the right thing!).&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Be open and receptive to feedback&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Commit code early and often&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Request code reviews from thorough critics.&lt;/li&gt;
&lt;li&gt;Ask to bounce ideas off your teammates.&lt;/li&gt;
&lt;li&gt;Design the interface or API of a new system first.&lt;/li&gt;
&lt;li&gt;Send out a design document before devoting your energy to your code.&lt;/li&gt;
&lt;li&gt;If possible, structure ongoing projects so that there is some shared context with your teammates.&lt;/li&gt;
&lt;li&gt;Solicit buy-in for controversial features before investing too much time.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Build feedback loops&lt;/em&gt; for your decisions.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="improve-your-project-estimation-skills"&gt;Improve your project estimation skills&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Project estimation is one of the hardest skills that an effective engineer needs to learn. &lt;em&gt;But it’s crucial to master&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;To produce accurate estimates:
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Decompose the project&lt;/em&gt; into granular tasks.&lt;/li&gt;
&lt;li&gt;Estimate based on how long tasks will take, not on how long you or someone else wants them to take.&lt;/li&gt;
&lt;li&gt;Think of estimates as &lt;em&gt;probability distributions&lt;/em&gt;, not best-case scenarios.&lt;/li&gt;
&lt;li&gt;Let the person doing the actual task make the estimate.&lt;/li&gt;
&lt;li&gt;Beware of anchoring bias (e.g., hearing about a low estimate from someone else may skew our own estimate later to be too low).&lt;/li&gt;
&lt;li&gt;Use multiple approaches to estimate the same task (e.g., estimate from past experiences, decompose a larger task into smaller ones and estimate each one).&lt;/li&gt;
&lt;li&gt;Beware the mythical man-month. Having more people on a project doesn’t necessarily mean it will be completed faster. Communication overhead, context switching, and other factors can slow down the team as much as $O(n^2)$ considering the number of people involved.&lt;/li&gt;
&lt;li&gt;Validate estimates against historical data.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;time-boxing&lt;/strong&gt; to constrain tasks that can grow in scope.&lt;/li&gt;
&lt;li&gt;Allow others to challenge estimates.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Allow buffer room for the unknown in the schedule.&lt;/li&gt;
&lt;li&gt;Define measurable milestones.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Do the riskiest tasks first&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Know the limits of overtime.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="part-3-build-long-term-value"&gt;Part 3: Build Long-Term Value&lt;/h2&gt;
&lt;h3 id="balance-quality-with-pragmatism"&gt;Balance Quality with Pragmatism&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Establish a culture of reviewing code.
&lt;ul&gt;
&lt;li&gt;Catch bugs or design shortcomings early.&lt;/li&gt;
&lt;li&gt;Increase accountability for code changes.&lt;/li&gt;
&lt;li&gt;Provide an avenue for sharing best practices.&lt;/li&gt;
&lt;li&gt;Increase long-term agility.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Invest in good software abstractions to simplify difficult problems.
&lt;ul&gt;
&lt;li&gt;Reduce the complexity of the original problem into easier-to-understand primitives.&lt;/li&gt;
&lt;li&gt;Reduce future application maintenance and make it easier to apply future improvements.&lt;/li&gt;
&lt;li&gt;DRY principle: solve the hard problems once and re-use the solutions multiple times.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Scale code quality with &lt;em&gt;automated testing&lt;/em&gt;.
&lt;ul&gt;
&lt;li&gt;Make large refactors with confidence.&lt;/li&gt;
&lt;li&gt;Offer executable documentation.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Don&amp;rsquo;t test everything&lt;/em&gt;: focus on the most important parts of the code and make sure everything critical is covered.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Manage your technical debt.
&lt;ul&gt;
&lt;li&gt;Pay it periodically. Incur it when necessary to meet deadlines.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Repay the debts with the highest interest rates first&lt;/em&gt; (i.e., focus on what produces the most leverage).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="minimize-operational-burden"&gt;Minimize Operational Burden&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Embrace operational simplicity.
&lt;ul&gt;
&lt;li&gt;Increased complexity introduces more potential single points of failure.&lt;/li&gt;
&lt;li&gt;New engineers face a steeper learning curve when learning and understanding the new systems.&lt;/li&gt;
&lt;li&gt;Effort towards improving abstractions, libraries, and tools gets diluted across the different systems.&lt;/li&gt;
&lt;li&gt;Do the simple thing first.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build systems to fail fast&lt;/strong&gt;.
&lt;ul&gt;
&lt;li&gt;Pinpoint the source of errors.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Make debugging easier&lt;/em&gt; by not masking your errors and by not deferring failures until later.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Relentlessly automate&lt;/strong&gt;.
&lt;ul&gt;
&lt;li&gt;Don&amp;rsquo;t underestimate the future frequency of the task.&lt;/li&gt;
&lt;li&gt;Internalize the time savings over a long time horizon.&lt;/li&gt;
&lt;li&gt;Automate mechanics over decision-making.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Make batch processes idempotent&lt;/strong&gt;.
&lt;ul&gt;
&lt;li&gt;At least, make processes &lt;em&gt;&amp;ldquo;retryable&amp;rdquo;&lt;/em&gt; if they cannot be made idempotent.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Hone your ability to respond and recover quickly.
&lt;ul&gt;
&lt;li&gt;Plan and practice failure modes.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="invest-in-your-teams-growth"&gt;Invest in Your Team’s Growth&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Make hiring everyone’s responsibility.
&lt;ul&gt;
&lt;li&gt;Identify which qualities you care about the most in your colleagues: &lt;em&gt;keep the bar high&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Make sure the recruiting process is effective.&lt;/li&gt;
&lt;li&gt;Adapt interview problems to different levels of difficulty based on the candidate&amp;rsquo;s experience.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Design a good onboarding process.
&lt;ul&gt;
&lt;li&gt;Ramp up new engineers as quickly as possible (e.g., with &lt;em&gt;codelabs&lt;/em&gt;).
&lt;ul&gt;
&lt;li&gt;Help the people around you be successful.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Impart the team’s culture and values (e.g., have &lt;em&gt;onboarding talks&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;Expose new engineers to the breadth of fundamentals needed to succeed (e.g., with &lt;em&gt;mentorship&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;Socially integrate new engineers onto the team (e.g., give them starter tasks to feel they&amp;rsquo;re part of the team faster).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Share ownership of code (increase the &lt;em&gt;bus factor&lt;/em&gt;).
&lt;ul&gt;
&lt;li&gt;Avoid one-person teams.&lt;/li&gt;
&lt;li&gt;Review each other’s code and software designs.&lt;/li&gt;
&lt;li&gt;Make sure the team is exposed to different types of tasks and responsibilities regularly.&lt;/li&gt;
&lt;li&gt;Focus on readable and high-quality code.&lt;/li&gt;
&lt;li&gt;Share software decisions and architecture clearly.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Make sure code and workflows are documented properly&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Invest in teaching and mentoring&lt;/em&gt;.
&lt;ul&gt;
&lt;li&gt;The more effective the team becomes, the more freedom there is to work on new projects.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Build collective wisdom with post-mortems (e.g., at NASA, these become the user manual).&lt;/li&gt;
&lt;li&gt;Build a great engineering culture.
&lt;ul&gt;
&lt;li&gt;Optimize for iteration speed.&lt;/li&gt;
&lt;li&gt;Always automate.&lt;/li&gt;
&lt;li&gt;Use the right software abstractions.&lt;/li&gt;
&lt;li&gt;Nurture a respectful working environment.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Allot experimentation time&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Foster a culture of learning&lt;/em&gt; (CI/CD: &lt;em&gt;continuous improvement, continuous delightfulness&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hire the best&lt;/strong&gt;!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Edmond opened my eyes to the importance of being an effective engineer, showing what that entails and how to concretely achieve it. His extraordinary technical journey and knowledge of the field are evident throughout the book, and I found myself nodding in agreement with many of his points. He made me think about how I could apply his advice to my own work. Notably, the key concept of &lt;strong&gt;leverage&lt;/strong&gt; resonated deeply with me and ended up being the cornerstone of &lt;a href="https://www.sglavoie.com/posts/building-task-planner-react-native/"&gt;the final project I built for my bachelor&amp;rsquo;s degree&lt;/a&gt;: &lt;em&gt;ProductiviDo - An efficient task planner focusing on high-leverage activities&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="resources-and-references"&gt;Resources and references&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://edmondlau.co/"&gt;Edmond Lau&lt;/a&gt; - The author&amp;rsquo;s personal website.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.sglavoie.com/posts/building-task-planner-react-native/"&gt;ProductiviDo&lt;/a&gt; - A simple and efficient todo-app built with React-Native.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.goodreads.com/book/show/25238425-the-effective-engineer"&gt;The Effective Engineer: How to Leverage Your Efforts In Software Engineering to Make a Disproportionate and Meaningful Impact&lt;/a&gt; - goodreads.com.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.effectiveengineer.com/"&gt;The Effective Engineer&lt;/a&gt; - by Edmond Lau.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Sébastien Lavoie</dc:creator><pubDate>Sun, 16 Apr 2023 14:49:00 -0600</pubDate><guid isPermaLink="false">https://www.sglavoie.com/posts/book-summary-the-effective-engineer/</guid><category>learnings</category><category>advice</category><category>best practices</category><category>books</category><category>career</category><category>skimming-notes</category></item></channel></rss>