<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mark Adel</title>
    <description>The latest articles on DEV Community by Mark Adel (@markadel).</description>
    <link>https://dev.to/markadel</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F40906%2F80888d4b-0f7e-40ed-93f3-32ad1ce080a9.png</url>
      <title>DEV Community: Mark Adel</title>
      <link>https://dev.to/markadel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/markadel"/>
    <language>en</language>
    <item>
      <title>A Philosophy of Software Design Summary</title>
      <dc:creator>Mark Adel</dc:creator>
      <pubDate>Sat, 19 Jul 2025 12:18:15 +0000</pubDate>
      <link>https://dev.to/markadel/a-philosophy-of-software-design-summary-pk9</link>
      <guid>https://dev.to/markadel/a-philosophy-of-software-design-summary-pk9</guid>
      <description>&lt;p&gt;I just finished reading A Philosophy of Software Design by John Ousterhout.&lt;/p&gt;

&lt;p&gt;It was an easy and insightful read. The book reinforced many concepts I was already familiar with and introduced a few new ideas that I was able to immediately apply at work to reduce complexity and write more maintainable code.&lt;/p&gt;

&lt;p&gt;I carefully prepared this summary of the book using AI, ensuring that it captures all the main ideas.&lt;/p&gt;

&lt;p&gt;In my opinion, this summary doesn’t substitute for reading the book itself. I still highly recommend reading it in full.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chapter 1, 2: Introduction and The Nature of Complexity
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;As programs evolve and gain features, complexity accumulates, making them harder to understand and modify, which slows down development and leads to bugs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Symptoms of Complexity&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Change Amplification&lt;/strong&gt;: A simple change requires modifications in many different places.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cognitive Load&lt;/strong&gt;: Developers need to know a lot of information to complete a task, increasing learning time and bug risk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unknown Unknowns&lt;/strong&gt;: It's not clear what code to modify or what information is needed for a task, leading to bugs discovered only after changes are made.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Causes of Complexity&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dependencies&lt;/strong&gt;: When a piece of code cannot be understood or modified in isolation, requiring consideration or modification of other code. Dependencies should be minimized, and made obvious when introduced.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Obscurity&lt;/strong&gt;: Important information is not obvious, often due to vague names, missing documentation, or inconsistencies.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Complexity is Incremental&lt;/strong&gt;: Complexity accumulates in small chunks (hundreds or thousands of small dependencies and obscurities), making it hard to control and eliminate unless a "zero tolerance" philosophy is adopted.&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Chapter 3: Working Code Isn't Enough
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tactical Programming&lt;/strong&gt;: Focuses on getting features working as quickly as possible, often leading to short-sighted decisions and the rapid accumulation of small complexities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strategic Programming&lt;/strong&gt;: Requires an investment mindset where time is spent on improving system design and fixing problems, even if it slows down short-term progress. The primary goal is a great design that also works.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Benefits of Strategic Programming&lt;/strong&gt;: Proactive investments (e.g., finding simple designs, writing good documentation) and reactive investments (fixing design problems when discovered) lead to continuous improvements and long-term development speed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Investment Amount&lt;/strong&gt;: Spending about 10-20% of total development time on design investments, which quickly pays for itself and leads to faster development in the long run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistency is Crucial&lt;/strong&gt;: It's important to be consistent in applying the strategic approach and to see investment as a continuous, not delayed, task.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Chapter 4: Modules Should Be Deep
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Interface vs. Implementation&lt;/strong&gt;: Each module has an interface (what a developer needs to know to use it, describing what it does) and an implementation (how it fulfills its promises). Developers should only need to understand the interface of other modules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Formal and Informal Interface Elements&lt;/strong&gt;: Formal parts are explicitly specified in code (e.g., method signatures, public variables) and can be checked by the language. Informal parts (e.g., high-level behavior, usage constraints) are described in comments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Abstractions&lt;/strong&gt;: A simplified view of an entity that omits unimportant details, making complex things easier to think about. A module's interface is its abstraction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deep Modules&lt;/strong&gt;: Modules that provide powerful functionality but have simple interfaces. Their interfaces are much simpler than their implementations, hiding significant complexity and allowing internal changes without affecting other modules. Examples include:

&lt;ul&gt;
&lt;li&gt;Unix I/O. A deep interface with five simple system calls (&lt;code&gt;open&lt;/code&gt;, &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;write&lt;/code&gt;, &lt;code&gt;lseek&lt;/code&gt;, &lt;code&gt;close&lt;/code&gt;) that hide hundreds of thousands of lines of complex implementation code.&lt;/li&gt;
&lt;li&gt;Garbage Collector. A module with no interface that works invisibly to reclaim memory, effectively shrinking the overall system interface by eliminating the need for explicit object freeing.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Shallow Modules&lt;/strong&gt;: Modules whose interfaces are relatively complex compared to the functionality they provide, hiding little complexity. They offer little leverage against complexity. Examples enclude:

&lt;ul&gt;
&lt;li&gt;A class that only contains one static method, &lt;code&gt;formatToUpperCase(String input)&lt;/code&gt;. This class is shallow because it introduces the overhead of a new class and method call for functionality that could easily be a one-liner&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;LoggingService&lt;/code&gt; class that simply calls &lt;code&gt;System.out.println()&lt;/code&gt; for every method, without adding any actual logging logic.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Classitis&lt;/strong&gt;: A syndrome where classes are designed to be excessively small, based on the mistaken view that "more classes are better." This results in many shallow classes, increasing overall system complexity due to numerous interfaces and verbose code.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Example: Java I/O&lt;/strong&gt;: The Java class library often exhibits classitis, requiring developers to compose multiple objects (e.g., &lt;code&gt;FileInputStream&lt;/code&gt;, &lt;code&gt;BufferedInputStream&lt;/code&gt;, &lt;code&gt;ObjectInputStream&lt;/code&gt;) for a simple task like reading serialized objects, making it verbose and error-prone.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Conclusion&lt;/strong&gt;: Users of a module need only understand the abstraction provided by its interface. The most important issue in designing classes and other modules is to make them deep, so that they have simple interfaces for the common use cases, yet still provide significant functionality.&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Chapter 5: Information Hiding (and Leakage)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Information Hiding&lt;/strong&gt;: A crucial technique for creating deep modules, where each module encapsulates design decisions (knowledge) within its implementation, preventing it from appearing in its interface and thus being invisible to other modules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Benefits of Information Hiding&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Simplifies the module's interface, reducing cognitive load for users.&lt;/li&gt;
&lt;li&gt;Makes system more robust and scalable by reducing dependencies.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Information Leakage&lt;/strong&gt;: Occurs when a design decision is reflected in multiple modules, creating dependencies.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Addressing Information Leakage&lt;/strong&gt;: Reorganize classes so that knowledge affects only a single class, possibly by merging closely tied classes or creating a new class that encapsulates the leaked information with a simple, abstract interface.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Taking it Too Far&lt;/strong&gt;: Information hiding is only beneficial if the information is genuinely not needed outside the module. If critical information (e.g., performance-affecting configuration parameters) is hidden, it can impede proper use and tuning. The goal is to minimize &lt;em&gt;needed&lt;/em&gt; external information.&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Chapter 6: General-Purpose Modules are Deeper
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Specialization Leads to Complexity&lt;/strong&gt;: Over-specialization is a significant cause of complexity in software.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generality Simplifies Code&lt;/strong&gt;: More general-purpose code is simpler, cleaner, and easier to understand, applying at all levels of software design (classes, methods, eliminating special cases in code).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Somewhat General-Purpose Classes&lt;/strong&gt;: The "sweet spot" is to implement new modules in a "somewhat general-purpose" fashion. Functionality should meet current needs, but the interface should be general enough for multiple potential uses, without being tied specifically to current needs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generality and Information Hiding&lt;/strong&gt;: General-purpose APIs lead to better information hiding by separating concerns and encapsulating specific details where they belong. False abstractions hide information that users actually need, creating obscurity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Questions for General-Purpose Design&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;What is the simplest interface that covers all current needs? (Aim for fewer, more general-purpose methods but without sacrificing ease of use)&lt;/li&gt;
&lt;li&gt;In how many situations will this method be used? (If a method is designed for one particular use, it may be too special-purpose. See if you can replace several special-purpose methods with a single general-purpose method)&lt;/li&gt;
&lt;li&gt;Is this API easy to use for my current needs? (If too much additional code is needed, the API may be too general or provide the wrong functionality)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Push Specialization Upwards (and Downwards!)&lt;/strong&gt;: Specialized code should be cleanly separated from general-purpose code by pushing it higher (into application-specific classes, like UI code) or lower (into specific implementations, like device drivers).&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Chapter 7: Different Layer, Different Abstraction
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Layered System Design&lt;/strong&gt;: Software systems are typically composed in layers, where higher layers use the facilities of lower layers. In good design, each layer provides a distinct abstraction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Red Flag: Similar Abstractions in Adjacent Layers&lt;/strong&gt;: If adjacent layers have similar abstractions, it's a red flag indicating a problem with class decomposition.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pass-Through Methods&lt;/strong&gt;: A common manifestation of similar abstractions in adjacent layers. A method does little more than invoke another method with a similar or identical signature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Problems with Pass-Through Methods&lt;/strong&gt;: They make classes shallower, increase interface complexity, and create dependencies between classes (if the underlying method's signature changes, the pass-through method must also change). They indicate confusion over class responsibility.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solutions for Pass-Through Methods&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Expose the lower-level class directly to callers.&lt;/li&gt;
&lt;li&gt;Redistribute functionality between classes for distinct responsibilities.&lt;/li&gt;
&lt;li&gt;Merge classes if they cannot be cleanly disentangled.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;When Interface Duplication is OK&lt;/strong&gt;: It's acceptable if each method provides useful and distinct functionality, even with similar signatures (e.g., dispatchers that choose one of several methods to invoke, or different implementations of the same interface).&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Decorators (Wrappers)&lt;/strong&gt;: A design pattern that extends an object's functionality by wrapping it, often providing a similar API to the underlying object.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Problems with Decorators&lt;/strong&gt;: They tend to be shallow, introducing boilerplate for little new functionality, and often contain many pass-through methods. Overuse can lead to an explosion of shallow classes.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Alternatives to Decorators&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Add new functionality directly to the underlying class if general-purpose or naturally related.&lt;/li&gt;
&lt;li&gt;Merge specialized functionality with the use case.&lt;/li&gt;
&lt;li&gt;Merge with an existing decorator to create a deeper one.&lt;/li&gt;
&lt;li&gt;Implement as a standalone class if truly independent.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Interface vs. Implementation Abstraction&lt;/strong&gt;: The interface of a class should usually differ from its internal implementation. If they are too similar, the class is likely shallow. Good design encapsulates implementation complexity behind a simpler, higher-level interface.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Conclusion&lt;/strong&gt;: Every design element adds complexity; it must eliminate more complexity than it introduces to be worthwhile.&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Chapter 8: Pull Complexity Downwards
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Developer Responsibility&lt;/strong&gt;: Module developers should strive to make life as easy as possible for the users of their module, even if it means extra work for themselves.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple Interface over Simple Implementation&lt;/strong&gt;: It is more important for a module to have a simple interface than a simple implementation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoiding "Punting" Complexity&lt;/strong&gt;: Developers often "punt" hard problems (e.g., throwing exceptions, exporting configuration parameters) to callers or administrators, which simplifies their own code in the short term but amplifies complexity across the system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Example: Configuration Parameters&lt;/strong&gt;: Exporting configuration parameters (e.g., cache size, retry interval) can be a form of moving complexity upwards. 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?”. Avoid configuration parameters where possible. If necessary, provide reasonable defaults. Each module should aim for a complete solution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Taking it Too Far&lt;/strong&gt;: Pulling complexity downward is beneficial if the complexity is closely related to the class's existing functionality, simplifies other parts of the application, and simplifies the class's interface. Overdoing it can lead to information leakage or a single overly complex class.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conclusion&lt;/strong&gt;: When developing a module, look for opportunities to take a little bit of extra suffering upon yourself in order to reduce the suffering of your users.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Chapter 9: Better Together Or Better Apart?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fundamental Question&lt;/strong&gt;: Should two pieces of functionality be implemented together or separately? The goal is to reduce overall system complexity and improve modularity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bring Together if Information is Shared&lt;/strong&gt;: If multiple methods or classes share knowledge of a particular format or structure, combining them reduces information leakage and simplifies code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bring Together to Simplify the Interface&lt;/strong&gt;: Combining modules can lead to a simpler, more unified interface, potentially eliminating intermediate interfaces or allowing functionality to be performed automatically (e.g., buffering in file I/O).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bring Together to Eliminate Duplication&lt;/strong&gt;: If the same code pattern repeats, factor it out into a separate method or refactor the code so the snippet is executed in one place.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separate General-Purpose and Special-Purpose Code&lt;/strong&gt;: A general-purpose mechanism should not include code specialized for a particular use; specialized code should reside in different modules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Splitting and Joining Methods&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Length is Not the Only Criterion&lt;/strong&gt;: Long methods aren't inherently bad if they have simple signatures and are easy to read. Excessive splitting introduces unnecessary interfaces and separates related code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Splitting by Factoring Out Subtask&lt;/strong&gt;: Best when a subtask is cleanly separable and could be used by other methods.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Splitting into Separate Methods&lt;/strong&gt;: Makes sense if the original method had an overly complex interface doing multiple unrelated things. Each new method should have a simpler interface, and most callers should only need one of the new methods.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Joining Methods&lt;/strong&gt;: Can replace shallow methods with deeper ones, eliminate duplication, remove dependencies, or simplify interfaces.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;A Different Opinion: Clean Code&lt;/strong&gt;: Robert Martin advocates for extremely short functions (even 10 lines is too long), using method names as comments. Ousterhout argues that this often leads to numerous shallow functions, increasing interfaces and making it harder to understand how they work together. Depth is more important than length.&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Chapter 10: Define Errors Out Of Existence
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Exceptions and Complexity&lt;/strong&gt;: Exception handling is a major source of complexity. Code dealing with special conditions is inherently harder to write and test, and developers often define exceptions without considering their handling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Problems with Exceptions&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Disrupt normal control flow, requiring complex recovery or state restoration.&lt;/li&gt;
&lt;li&gt;Can create secondary exceptions during recovery (e.g., resending a delayed packet, corrupted redundant copy).&lt;/li&gt;
&lt;li&gt;Verbose and clunky language support makes code hard to read and trace.&lt;/li&gt;
&lt;li&gt;Difficult to test, as uncommon exceptions are hard to generate, leading to undetected bugs that surface rarely but catastrophically.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Too Many Exceptions&lt;/strong&gt;: Programmers often define unnecessary exceptions out of an "over-defensive" style, leading to a proliferation of exceptions that complicate system.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Punting Problems&lt;/strong&gt;: Throwing exceptions to avoid difficult situations passes the complexity to the caller, who often faces the same difficulty in handling it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interface Complexity&lt;/strong&gt;: Exceptions are part of a class's interface and add complexity, making classes shallower.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Techniques to Reduce Exception Handling Complexity&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Define Errors Out Of Existence&lt;/strong&gt;: The best way is to modify API semantics so that certain error conditions are no longer exceptional.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Java &lt;code&gt;substring&lt;/code&gt; Method&lt;/strong&gt;: The Java &lt;code&gt;substring&lt;/code&gt; throwing &lt;code&gt;IndexOutOfBoundsException&lt;/code&gt; is unnecessary. It should automatically adjust indices to the string bounds and return the overlapping portion, defining the exception out of existence and simplifying usage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Windows vs. Unix File Deletion&lt;/strong&gt;: Windows prohibits deleting open files (an error), while Unix marks them for deletion and allows open processes to continue access, delaying the actual deletion until all references are closed, thus defining the "file in use" error out of existence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Catching Bugs vs. Complexity&lt;/strong&gt;: While exceptions can catch some bugs, they also increase complexity, leading to other bugs. Simpler software with fewer error conditions generally has fewer bugs.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Mask Exceptions&lt;/strong&gt;: Handle exceptional conditions at a low level in the system so higher levels are unaware.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TCP Example&lt;/strong&gt;: TCP masks packet loss by retransmitting internally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NFS Example&lt;/strong&gt;: NFS masks server crashes by retrying requests, causing applications to hang but allowing them to seamlessly resume when the server recovers, avoiding cascading application failures.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Exception Aggregation&lt;/strong&gt;: Handle many exceptions with a single piece of code.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Web Server Parameter Handling&lt;/strong&gt;: Instead of multiple &lt;code&gt;try-catch&lt;/code&gt; blocks for missing parameters, a single top-level handler in the dispatcher can catch all such exceptions and generate a generic error response, with specific error messages included in the exception object. This approach encapsulates knowledge effectively.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Just Crash?&lt;/strong&gt;: For errors that are difficult/impossible to handle and occur infrequently (e.g., out of memory, disk hard errors), the simplest approach is to print diagnostic information and abort the application. This avoids adding significant complexity for rare, unrecoverable situations.&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Taking it Too Far&lt;/strong&gt;: Defining away or masking exceptions only makes sense if the exception information is not needed outside the module. If it's essential for robustness (e.g., knowing about network failures in a communication module), then the exceptions must be exposed.&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Chapter 11: Design it Twice
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Principle&lt;/strong&gt;: It's unlikely that the first design idea will be the best one; considering multiple options for each major design decision leads to better results.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Process&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Sketch out a few radically different design possibilities, focusing on the most important aspects (e.g., a class's interface).&lt;/li&gt;
&lt;li&gt;Even if one approach seems superior, explore a second to understand its weaknesses and learn from the comparison.&lt;/li&gt;
&lt;li&gt;List pros and cons for each alternative, prioritizing ease of use for higher-level software. Consider interface simplicity, generality, and efficiency.&lt;/li&gt;
&lt;li&gt;The best design might combine features from different alternatives or be an entirely new solution driven by identified problems.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Chapter 12: Why Write Comments? The Four Excuses
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Importance of Comments&lt;/strong&gt;: In-code documentation is crucial for understanding a system, working efficiently, defining abstractions, hiding complexity, and improving design.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Common Excuses for Not Writing Comments (and Rebuttals)&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Good code is self-documenting."&lt;/strong&gt;: A myth. While good naming helps, much design information (informal interface aspects, rationale, usage constraints) cannot be expressed in code and requires comments. Without comments, abstractions are lost, forcing developers to read implementation details.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"I don't have time to write comments."&lt;/strong&gt;: This conflicts with the "investment mindset." Good comments significantly improve maintainability and quickly pay for themselves. Writing comments for abstractions early on (as part of design) also improves the overall design. Commenting typically adds no more than 10% to development time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Comments get out of date and become misleading."&lt;/strong&gt;: While true, it's a manageable problem. Updating comments doesn't require enormous effort for large code changes. Organizing documentation to avoid duplication and keeping comments near relevant code, along with code reviews, helps maintain accuracy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"All the comments I have seen are worthless; why bother?"&lt;/strong&gt;: This highlights a problem with &lt;em&gt;how&lt;/em&gt; comments are written, not their inherent value.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Benefits of Well-Written Comments&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Capture information from the designer's mind that code cannot express, aiding future understanding and modification.&lt;/li&gt;
&lt;li&gt;Reduce cognitive load by providing necessary information and allowing readers to ignore irrelevant details.&lt;/li&gt;
&lt;li&gt;Reduce "unknown unknowns" by clarifying system structure and relevant code/information for changes.&lt;/li&gt;
&lt;li&gt;Clarify dependencies and eliminate obscurity.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;A Different Opinion: Robert Martin's "Comments are Failures"&lt;/strong&gt;: Martin argues comments compensate for code's lack of expressiveness and are "failures." Ousterhout disagrees, stating comments provide information &lt;em&gt;different&lt;/em&gt; from code (e.g., abstractions) and are essential for defining abstractions and managing complexity. Replacing comments with short, named methods often leads to cryptic code.&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Chapter 13: Comments Should Describe Things that Aren’t Obvious from the Code
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Guiding Principle&lt;/strong&gt;: Comments should describe information that cannot be easily deduced or is not immediately obvious from the code itself. This includes low-level details (e.g., boundary conditions, units), rationale, rules, and especially abstractions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why Code Alone Is Insufficient for Abstractions&lt;/strong&gt;: Code is too low-level and includes implementation details that should be hidden in an abstraction. Comments are necessary to provide the simplified, higher-level view that users need.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conventions for Commenting&lt;/strong&gt;: Adopt conventions for what to comment and formatting (e.g., Javadoc, Doxygen) to ensure consistency and encourage actual comment writing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't Repeat the Code&lt;/strong&gt;: Avoid comments that merely restate what is obvious from the code. Such comments are unhelpful and reinforce the idea that comments are worthless. Use different words in comments than in names to provide additional meaning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lower-Level Comments Add Precision&lt;/strong&gt;: These comments clarify exact meanings and are useful for variable declarations, specifying units, boundary conditions (inclusive/exclusive), null implications, resource ownership, and invariants. They fill in details missing from names/types.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Higher-Level Comments Enhance Intuition&lt;/strong&gt;: These comments are more abstract, omit details, and help readers understand the overall intent and structure (the "what" and "why," not the "how"). Useful for blocks of code and interface comments.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Explaining "Why"&lt;/strong&gt;: Comments can explain the rationale or conditions under which code is executed (e.g., "how we get here").&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Interface Documentation&lt;/strong&gt;: Essential for defining abstractions. It should be separated from implementation comments.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Class Interface Comment&lt;/strong&gt;: High-level description of the class's abstraction, capabilities, and instance representation, possibly limitations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Method Interface Comment&lt;/strong&gt;: Describes behavior for callers, arguments, return value, side effects, exceptions, and preconditions. Should provide all information needed to invoke without reading implementation.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Implementation Comments: What and Why, Not How&lt;/strong&gt;: Focus on what code blocks are doing (abstract description) and why (tricky aspects, bug fixes), rather than how they work. Most short, simple methods don't need them.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Cross-Module Design Decisions&lt;/strong&gt;: Document complex design decisions affecting multiple classes in a central, discoverable place (e.g., a &lt;code&gt;designNotes&lt;/code&gt; file with short in-code references).&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Comments Belong in the Code, Not the Commit Log&lt;/strong&gt;: Detailed information about code changes and their motivations should be in the code itself, not just commit messages. Commit logs are not easily discoverable or browsable for ongoing development.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Conclusion&lt;/strong&gt;: Comments ensure code is obvious to readers, filling in information not apparent from the code.&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Chapter 14: Choosing Names
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Importance of Good Names&lt;/strong&gt;: Good names are a form of documentation; they make code easier to understand, reduce the need for other documentation, and help detect errors. Poor names increase complexity, ambiguities, and bugs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complexity is Incremental (Naming)&lt;/strong&gt;: While one mediocre name won't destroy a system, thousands of bad names significantly impact complexity and manageability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create an Image&lt;/strong&gt;: The goal of naming is to create a clear image in the reader's mind about the entity's nature, what it is, and what it is not. Names are a form of abstraction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Names Should Be Precise&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Avoid Vagueness/Generality&lt;/strong&gt;: Generic or vague names (e.g., "count," "x," "y," "status," "result") obscure meaning and lead to misuse.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Predicates for Booleans&lt;/strong&gt;: Boolean variable names should clearly indicate true/false meaning (e.g., &lt;code&gt;cursorVisible&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoid Ambiguous Similarities&lt;/strong&gt;: Names that are too similar (e.g., &lt;code&gt;struct socket&lt;/code&gt; and &lt;code&gt;struct sock&lt;/code&gt; in Linux kernel) cause confusion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exceptions for Short Loops&lt;/strong&gt;: Generic names like &lt;code&gt;i&lt;/code&gt; and &lt;code&gt;j&lt;/code&gt; are acceptable for short loop iteration variables where their scope is immediately visible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoid Over-Specificity&lt;/strong&gt;: A name being too specific limits the perceived use of a general-purpose entity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Red Flag: Hard to Pick Name&lt;/strong&gt;: Difficulty in finding a precise, intuitive, and concise name suggests a poor design or unclear purpose for the underlying entity.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Use Names Consistently&lt;/strong&gt;: For frequently used concepts, establish a consistent naming convention and stick to it. This reduces cognitive load and allows for safe assumptions.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Avoid Extra Words&lt;/strong&gt;: Every word should add useful information. Avoid generic nouns (e.g., "object," "field"). Also, don't repeat the class name in an instance variable name within that class.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Different Opinion: Go Style Guide&lt;/strong&gt;: The Go language advocates very short names, sometimes single characters, arguing that long names obscure code. Ousterhout finds this can lead to ambiguity and requires more mental effort to deduce meaning, preferring names that provide clearer clues.&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Chapter 15: Write The Comments First (Use Comments As Part Of The Design Process)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Delayed Comments are Bad Comments&lt;/strong&gt;: Postponing comment writing until after coding and testing leads to poor quality documentation because it's often never written, or written hastily when enthusiasm for the project has waned and design details are fuzzy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write the Comments First Approach&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Start with the class interface comment.&lt;/li&gt;
&lt;li&gt;Write interface comments and signatures for important public methods (leave bodies empty).&lt;/li&gt;
&lt;li&gt;Iterate on these comments until the basic structure feels right.&lt;/li&gt;
&lt;li&gt;Write declarations and comments for important class instance variables.&lt;/li&gt;
&lt;li&gt;Fill in method bodies, adding implementation comments and new method/variable comments as needed.&lt;/li&gt;
&lt;li&gt;When code is done, comments are done.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Benefits of Comments-First&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Better Comments&lt;/strong&gt;: Key design issues are fresh in mind, leading to more accurate and complete comments. Focus on abstraction before implementation details.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comments as a Design Tool&lt;/strong&gt;: The most important benefit. Writing comments forces identification of the essence of a variable or code, which is critical for good design. It allows for early evaluation and tuning of abstractions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Canary in the Coal Mine of Complexity&lt;/strong&gt;: A long or complicated comment for a method or variable is a "red flag" (Hard to Describe) indicating a shallow class, complex interface, or poor variable decomposition. This helps identify and fix design problems early.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Chapter 16: Modifying Existing Code
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Software Evolution&lt;/strong&gt;: Software systems evolve iteratively, with designs constantly changing. The design of a mature system is more a product of its evolution than initial conception.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stay Strategic&lt;/strong&gt;: When modifying existing code (bug fixes, new features), avoid the "smallest possible change" tactical mindset. This introduces special cases and complexity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strategic Approach to Modification&lt;/strong&gt;: Aim for the system to have the structure it would have had if the change was considered from the start. Refactor and improve the design with every modification. This is an investment that speeds up future development.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Addressing Constraints&lt;/strong&gt;: While tight deadlines or broad incompatibilities might necessitate quick fixes, always seek cleaner alternatives or plan for future refactoring. Organizations should allocate time for cleanup and refactoring.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Chapter 17: Consistency
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Definition&lt;/strong&gt;: Similar things are done in similar ways, and dissimilar things are done in different ways.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Benefits&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cognitive Leverage&lt;/strong&gt;: Once a pattern is learned, that knowledge can be reused to quickly understand other similar situations, reducing cognitive load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduces Mistakes&lt;/strong&gt;: Prevents incorrect assumptions based on familiar-looking but inconsistent patterns, making assumptions safer.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Examples of Consistency&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Names&lt;/strong&gt;: Using names consistently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coding Style&lt;/strong&gt;: Following style guides for indentation, brace placement, naming, commenting, etc., improves readability and reduces errors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interfaces&lt;/strong&gt;: Multiple implementations of the same interface reduce cognitive load because the common interface is already known.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design Patterns&lt;/strong&gt;: Using established solutions to common problems makes code more familiar and easier to understand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invariants&lt;/strong&gt;: Properties of variables/structures that are always true simplify reasoning about code behavior and reduce special cases.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Ensuring Consistency&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Document&lt;/strong&gt;: Create and maintain a document listing important overall conventions (e.g., style guides). Place it conspicuously and encourage team members to read it. For localized conventions (e.g., invariants), document them in the code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enforce&lt;/strong&gt;: Implement automated tools (e.g., pre-commit scripts) to check for and prevent violations of syntactic conventions. Code reviews also help enforce conventions and educate developers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"When in Rome..."&lt;/strong&gt;: Developers should follow existing conventions within a file or project. Look for existing patterns and mimic them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't Change Existing Conventions&lt;/strong&gt;: Resist the urge to "improve" on existing conventions unless there is significant new information and the new approach is demonstrably superior enough to warrant updating all old uses. Inconsistency is almost always worse than a slightly less optimal but consistent approach.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Taking it Too Far&lt;/strong&gt;: Consistency applied to dissimilar things creates complexity and confusion. Consistency benefits only when developers can confidently assume "if it looks like an x, it really is an x."&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Chapter 18: Code Should be Obvious
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Definition of "Obvious" Code&lt;/strong&gt;: Code is obvious if a reader can understand its behavior and meaning quickly, without much thought, and their initial guesses are correct.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reader's Perspective&lt;/strong&gt;: "Obvious" is in the mind of the reader. Code reviews are crucial for identifying nonobvious code. If a reviewer finds it nonobvious, it needs clarification.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Things That Make Code More Obvious&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Good Names&lt;/strong&gt;: Precise and meaningful names clarify code and reduce documentation needs. Vague names force readers to deduce meaning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistency&lt;/strong&gt;: Similar things done in similar ways allow readers to recognize patterns and draw safe conclusions quickly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Judicious Use of White Space&lt;/strong&gt;: Formatting (e.g., blank lines, indentation) improves readability by clarifying structure and making comments more visible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comments&lt;/strong&gt;: When code cannot be made obvious, comments are essential to provide missing information and clarify confusion.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Chapter 19: Software Trends
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Object-Oriented Programming and Inheritance&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Benefits&lt;/strong&gt;: Mechanisms like classes, private methods, and instance variables can help achieve better designs (e.g., information hiding through private elements).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interface Inheritance&lt;/strong&gt;: A parent class defines method signatures without implementation. Subclasses provide different implementations. This reduces complexity by reusing interfaces for multiple purposes, increasing an interface's "depth."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implementation Inheritance&lt;/strong&gt;: A parent class provides default implementations that subclasses can inherit or override. Reduces code duplication across subclasses, minimizing change amplification.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drawbacks of Implementation Inheritance&lt;/strong&gt;: Creates dependencies between parent and subclasses (e.g., parent's instance variables accessed by children), leading to information leakage. Modifying one class in the hierarchy often requires examining others, increasing complexity. Should be used with caution; composition may be a better alternative.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Agile Development&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Focus&lt;/strong&gt;: Primarily a process for software development (teams, schedules, testing, customer interaction), rather than design principles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incremental and Iterative Development&lt;/strong&gt;: Emphasizes developing in series of iterations, adding and evaluating features. This aligns with the book's advocacy for incremental design where abstractions are refined over time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Risk: Tactical Programming&lt;/strong&gt;: Agile can lead to tactical programming by focusing on features over abstractions and encouraging delayed design decisions. This can result in rapid complexity accumulation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ousterhout's Stance&lt;/strong&gt;: Increments of development should be &lt;em&gt;abstractions&lt;/em&gt;, not just features. Design abstractions cleanly and comprehensively when needed, rather than in small pieces over time.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Unit Tests&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shift in Practice&lt;/strong&gt;: Programmers now widely write unit tests, which are small, focused tests for single methods or small code sections.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Facilitates Refactoring&lt;/strong&gt;: Unit tests are crucial for enabling refactoring. A robust test suite provides confidence that structural changes won't introduce undetected bugs, encouraging developers to make design improvements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High Code Coverage&lt;/strong&gt;: Unit tests offer better code coverage than system tests, making them more effective at finding bugs.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Test-Driven Development (TDD)&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Process&lt;/strong&gt;: Write unit tests &lt;em&gt;before&lt;/em&gt; writing code; then write just enough code to make tests pass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Critique&lt;/strong&gt;: Ousterhout is not a fan. TDD focuses on getting features working, leading to tactical programming and potentially messy designs with no clear time for broader design.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alternative&lt;/strong&gt;: Design abstractions comprehensively when needed, rather than incrementally through tests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When TDD Makes Sense&lt;/strong&gt;: Useful for fixing bugs; write a failing unit test first, then fix the bug to make the test pass.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Design Patterns&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Purpose&lt;/strong&gt;: Commonly accepted solutions for particular problems (e.g., iterator, observer).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Benefits&lt;/strong&gt;: Generally provide clean solutions to common problems, making implementation faster and more reliable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Risk: Over-application&lt;/strong&gt;: Not every problem fits an existing pattern. Forcing a problem into an ill-fitting pattern can lead to more complexity than a custom approach. "More design patterns are not necessarily better."&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Conclusion&lt;/strong&gt;: Always challenge new software development paradigms from the standpoint of complexity, as some may inadvertently worsen complexity rather than improve it.&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Chapter 20: Designing for Performance
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Goal&lt;/strong&gt;: Achieve high performance without sacrificing clean design. Simplicity usually makes systems faster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How Much to Worry About Performance&lt;/strong&gt;: Avoid optimizing every statement (slows development, adds complexity, often ineffective). Also avoid completely ignoring it (leads to "death by a thousand cuts" with widespread inefficiencies). The best approach is to use basic knowledge of expensive operations to choose naturally efficient and simple designs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fundamentally Expensive Operations&lt;/strong&gt;: Network communication, I/O to secondary storage, dynamic memory allocation, cache misses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Learning Expensive Operations&lt;/strong&gt;: Use micro-benchmarks to measure the cost of single operations in isolation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complexity vs. Performance Trade-off&lt;/strong&gt;: If efficiency adds only minor, hidden complexity without affecting interfaces, it might be worthwhile. If it adds significant complexity or complicates interfaces, start with simpler design and optimize later if needed, unless performance is clearly critical from the outset.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure Before (and After) Modifying&lt;/strong&gt;: Always measure system behavior before making performance tweaks; programmers' intuitions are unreliable. Measurements identify bottlenecks and provide a baseline to confirm improvements. If no measurable difference, revert changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Chapter 21: Decide What Matters
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Core Principle&lt;/strong&gt;: Separate what matters from what doesn't. Structure software systems around the important things, minimizing the impact of less important things. Emphasize what matters (make it obvious), and hide what doesn't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relevance to Earlier Concepts&lt;/strong&gt;: This principle is fundamental to abstraction (interface reflects what matters to users), naming (names convey essential information).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How to Decide What Matters&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Leverage Points&lt;/strong&gt;: Look for solutions that solve multiple problems or pieces of information that clarify many other things (e.g., general-purpose interfaces, invariants).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple Options ("Design it Twice")&lt;/strong&gt;: Comparing different options helps identify what is truly most important.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hypothesis and Learning&lt;/strong&gt;: For less experienced developers, form a hypothesis about what matters, build based on it, and learn from the outcome (whether right or wrong).&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Emphasize Things That Matter&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prominence&lt;/strong&gt;: Place important things where they are easily seen (interface documentation, names, heavily used method parameters).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repetition&lt;/strong&gt;: Key ideas should appear repeatedly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Centrality&lt;/strong&gt;: Most important ideas should form the core structure of the system (e.g., device driver interfaces).&lt;/li&gt;
&lt;li&gt;Conversely, deemphasize what doesn't matter by hiding it and ensuring it doesn't impact system structure or frequent encounters.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Mistakes&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Treating Too Many Things as Important&lt;/strong&gt;: Clutters design, adds complexity, increases cognitive load (e.g., irrelevant method arguments, forcing awareness of unnecessary distinctions like buffered I/O, shallow classes).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failing to Recognize Importance&lt;/strong&gt;: Hides crucial information or functionality, impedes productivity, and leads to "unknown unknowns."&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Broader Application&lt;/strong&gt;: The principle applies beyond software design to technical writing (structuring around key concepts) and life philosophy (focusing energy on what truly matters).&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;"Good Taste"&lt;/strong&gt;: The ability to distinguish what is important from what is not important is a key attribute of a good software designer.&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Chapter 22: Conclusion
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Central Theme&lt;/strong&gt;: The book's focus is entirely on managing &lt;strong&gt;complexity&lt;/strong&gt;, which is the most significant challenge in software design, affecting buildability, maintainability, and performance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Key Takeaways&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Understanding the root causes of complexity.&lt;/li&gt;
&lt;li&gt;Recognizing "red flags".&lt;/li&gt;
&lt;li&gt;Applying general design principles.&lt;/li&gt;
&lt;li&gt;Adopting an "investment mindset" for design.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Payoff of Good Design&lt;/strong&gt;: Investments in good design pay off quickly through reusable modules, clearer documentation, and improved design skills, ultimately leading to faster development of higher-quality software and a more enjoyable process.&lt;/li&gt;

&lt;/ul&gt;

</description>
      <category>softwareengineering</category>
      <category>programming</category>
      <category>books</category>
    </item>
    <item>
      <title>Modern Software Engineering Chapter 4–8 Summary</title>
      <dc:creator>Mark Adel</dc:creator>
      <pubDate>Sat, 29 Mar 2025 21:54:31 +0000</pubDate>
      <link>https://dev.to/markadel/modern-software-engineering-chapter-4-8-summary-41fp</link>
      <guid>https://dev.to/markadel/modern-software-engineering-chapter-4-8-summary-41fp</guid>
      <description>&lt;p&gt;I recently finished reading &lt;em&gt;Modern Software Engineering&lt;/em&gt; by Dave Farley and would like to share summaries of the five chapters I found most useful, especially for new software engineers. In my opinion, reading these summaries can serve as a substitute for reading the full chapters. I generated each summary using ChatGPT and then refined them through several iterations to ensure they capture all the main ideas accurately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chapter 4 (Working Iteratively)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Iteration is a procedure that drives learning. Iteration allows us to learn, react, and adapt to what we have learned. Without iteration, and the closely related activity of collecting feedback, there is no opportunity to learn on an ongoing basis. Fundamentally, iteration allows us to make mistakes and to correct them, or make advances and enhance them.&lt;/li&gt;
&lt;li&gt;Iteration allows us to progressively approach some goal. Its real power is that it allows us to do this even when we don't really know how to approach our goals. As long as we have some way of telling whether we are closer to or further from our goal. We could even iterate randomly and still achieve our goal. We can discard the steps that take us further away and prefer the steps that move us nearer. This is in essence how evolution works. It is also at the heart of how modern machine learning (ML) works.&lt;/li&gt;
&lt;li&gt;We can't afford to spend lots of time in analysis and design without creating anything, because that means more time not learning what really works. We need to do just enough analysis, design, coding, testing, and releasing to get our ideas out into the hands of our customers and users so that we can see what really works. We need to reflect on that and then, given that learning, adapt what we do next to take advantage of it.&lt;/li&gt;
&lt;li&gt;Industry data says that for the best software companies in the world, two-thirds of their ideas produce zero or negative value. We are terrible at guessing what our users want. Even when we ask our users, they don't know what they want either. The most effective approach is to iterate. It is accepting that some, maybe even many, of our ideas will be wrong and work in a way that allows us to try them out as quickly, cheaply, and efficiently as possible.&lt;/li&gt;
&lt;li&gt;To make progress we must take a chance, make a guess, be willing to take a risk. We are very bad at guessing, though, so we need to work more defensively by proceeding in small steps and limit the scope of our guesses.&lt;/li&gt;
&lt;li&gt;If we work iteratively in small steps, the cost of any single step going wrong is inevitably lower.&lt;/li&gt;
&lt;li&gt;An iterative approach allows us to always have the most up-to-date picture of the situation that we are really in, rather than some predictive, theoretical, always-inaccurate version of that situation. It allows us to learn, react, and adapt as changes happen along the way. &lt;strong&gt;Working iteratively is the only effective strategy for a changing situation.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Practices like Continuous Integration and Continuous Delivery are inherently iterative, promoting frequent, small changes and continuous learning. This approach not only improves the quality of the code but also aligns development practices with the iterative mindset.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Chapter 5 (Feedback)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Without feedback, there is no opportunity to learn. We can only guess, rather than make decisions based on reality. Despite this, it is surprising how little attention many people and organizations pay to it.&lt;/li&gt;
&lt;li&gt;Software development is always an exercise in learning, and the environment in which it takes place is always changing; therefore, feedback is an essential aspect of any effective software development process.&lt;/li&gt;
&lt;li&gt;By following an approach like test-driven development, where tests are written and run before coding, fast, high-quality feedback can be achieved in milliseconds. This enables immediate validation of correctness, quickly catching mistakes and design issues.&lt;/li&gt;
&lt;li&gt;Continuous integration is about evaluating every change to the system along with every other change as frequently as possible, as close to "continuously" as we can practically get. Continuous integration, when practiced correctly, means that we get regular, frequent drips of feedback. It gives us powerful insight into the state of our code and the behavior of our system throughout the working day. For CI to work, we have to commit our changes frequently enough to gain that feedback. Not only does this approach mean that our software is always in a deployable state, but it also encourages us to design our work in a way that sustains this approach.&lt;/li&gt;
&lt;li&gt;A feedback-driven approach also shapes software architecture. Continuous delivery demands that software be constantly ready for release. This encourages architectures that are modular, testable, and easier to deploy.&lt;/li&gt;
&lt;li&gt;Feedback loops in product design validate the effectiveness of ideas by directly measuring customer usage and satisfaction. Continuous delivery supports this by allowing organizations to deploy and test ideas quickly, enabling faster learning about customer needs and product effectiveness.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Chapter 6 (Incrementalism)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;If working iteratively is about refining and improving something over a series of iterations, then working incrementally is about building a system, and ideally releasing it, piece by piece.&lt;/li&gt;
&lt;li&gt;To create complex systems, we need both approaches. An incremental approach allows us to decompose work and to deliver value step-by-step (incrementally), getting to value sooner and delivering value in smaller, simpler steps.&lt;/li&gt;
&lt;li&gt;Working in ways that allow us the freedom to change our code and change our minds as our understanding deepens is fundamental to good engineering and is what incrementalism is built upon. Striving to be able to work incrementally then is also striving for higher-quality systems. If your code is hard to change, it is low quality, whatever it does.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Chapter 7 (Empiricism)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Empiricism focuses on decision-making based on evidence and real-world observations. In engineering, especially software engineering, empiricism ensures that solutions are grounded in practical reality rather than abstract theories.&lt;/li&gt;
&lt;li&gt;Software systems often surprise developers when deployed in production. These surprises should be seen as opportunities to learn. Production environments expose the reality of a system's behavior, making it clear that software is a "best guess" at functionality. The true test of a system's quality comes in real-world usage, where unexpected behaviors are inevitable.&lt;/li&gt;
&lt;li&gt;While both experimentation and empiricism rely on observation, they serve different purposes. Experimentation tests a specific hypothesis under controlled conditions, while empiricism involves observing outcomes from real-world environments without the strict structure of a controlled experiment. Empiricism can validate or challenge assumptions based on broader, more informal observations. It is not a replacement for structured experimentation but rather complements it by allowing developers to observe results in less controlled but more realistic settings.&lt;/li&gt;
&lt;li&gt;Empiricism is essential in debugging and problem-solving. Assumptions based on prior experience can lead to incorrect conclusions, and only by gathering real-world evidence and thoroughly testing can the true nature of an issue be uncovered. Empirical investigation, rather than reliance on intuition or preconceived theories, leads to more accurate understanding and resolution of complex problems in software systems. This highlights the need to challenge assumptions and base decisions on observable facts.&lt;/li&gt;
&lt;li&gt;Human beings are prone to cognitive biases and shortcuts, often making us jump to conclusions based on incomplete data. These biases helped humans survive in primitive environments but can lead to faulty decisions in complex engineering problems. To avoid self-deception, it is crucial to gather organized, structured feedback from reality and resist the temptation to shape facts to fit preconceived theories. Richard Feynman famously characterized science as follows: &lt;em&gt;The first principle is that you must not fool yourself – and you are the easiest person to fool.&lt;/em&gt; Empiricism requires rigorous attention to facts and a willingness to challenge initial impressions.&lt;/li&gt;
&lt;li&gt;Developers may invent realities that suit their assumptions, particularly when working with complex systems. Theoretical models that seem correct in principle often fail when tested empirically.&lt;/li&gt;
&lt;li&gt;Empirical reality must guide decision-making, not assumptions or untested theories. Skepticism and evidence-based problem-solving should be central to development practices. Developers should assume their assumptions might be wrong and actively seek evidence to confirm or refute them.&lt;/li&gt;
&lt;li&gt;Engineering, unlike pure science, requires us to focus on the practicality of our solutions. This is where empiricism becomes crucial. It's not sufficient to simply observe the world, make assumptions based on those observations, and assume we are correct just because the data came from real-world sources. That approach leads to both bad science and poor engineering. Since engineering is a practical field, we must remain skeptical of our assumptions and continually test them through experiments, always verifying them against real-world experience.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Chapter 8 (Being Experimental)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Being experimental means moving away from decisions based on authority and instead basing decisions on empirical evidence. It encourages a shift in mindset where decisions in software development are made based on experiments and real-world evidence. For example, instead of debating whether Clojure is better than C#, small trials can test aspects like stability and performance to make informed choices.&lt;/li&gt;
&lt;li&gt;Feedback is central to being experimental in software development. Efficient, frequent feedback enables developers to quickly learn from the results of their work and adjust their approach. Whether the feedback comes from automated tests or user interactions, it must be gathered quickly and interpreted carefully to drive continuous improvement.&lt;/li&gt;
&lt;li&gt;Every experiment starts with a hypothesis—a proposed explanation or prediction that can be tested. In software engineering, hypotheses might be about the effectiveness of a certain architecture, the scalability of a system, or the speed of a specific code optimization. Hypotheses should be clear, specific, and measurable.&lt;/li&gt;
&lt;li&gt;Measurement is crucial for validating the predictions of hypotheses. However, poor measurements can mislead, as illustrated by a client case where incentivizing developers based on test coverage led to the creation of useless tests with no assertions. This example shows how focusing on the wrong metrics can skew results. The correct measurement in this case would have been software stability, not just the number of tests. A good experiment relies on accurate, meaningful metrics that reflect the true quality or effectiveness of the software.&lt;/li&gt;
&lt;li&gt;Controlling variables is necessary to gather reliable data from experiments. By controlling variables (like environment, input date, dependencies, hardware, etc.), you ensure that any changes in the outcome can be attributed to the variable you're testing — not something else. For example, if you're testing a new caching strategy, but the server load is fluctuating wildly, your results might be skewed. Controlling for load ensures the cache strategy is the real difference-maker.&lt;/li&gt;
&lt;li&gt;Automated tests are considered small-scale experiments that provide constant feedback on software quality. They can be seen as hypotheses about how the software should behave under certain conditions, and running these tests validates or invalidates the predictions.&lt;/li&gt;
&lt;li&gt;Different sciences, such as physics or biology, might have different degrees of precision, but they all benefit from systematic experimentation. This analogy is used to reinforce that while software experiments might not reach the precision of physics, they are still valuable for learning and improving. By organizing software development around experiments, teams can generate deeper insights into system behavior and make better decisions.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>softwareengineering</category>
      <category>programming</category>
      <category>books</category>
    </item>
    <item>
      <title>Make It Work, Then Make It Better</title>
      <dc:creator>Mark Adel</dc:creator>
      <pubDate>Wed, 27 Nov 2024 22:11:57 +0000</pubDate>
      <link>https://dev.to/markadel/make-it-work-then-make-it-better-5gm6</link>
      <guid>https://dev.to/markadel/make-it-work-then-make-it-better-5gm6</guid>
      <description>&lt;p&gt;In software engineering, "Make it work, then make it better" is a pragmatic approach to development. It stresses the importance of first building a working solution before refining and optimizing it. It consists of two phases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The first phase focuses on ensuring the software works as intended, delivering the expected results and meeting the requirements. At this stage, we make the "computer" understand the code.&lt;/li&gt;
&lt;li&gt;The second phase shifts the focus to humans, refactoring the code for your future self and fellow developers who will read, use, and maintain it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This doesn't mean ignoring code quality in the first phase; it might take around 30% of the focus, depending on context.&lt;/p&gt;

&lt;p&gt;Needless to say, phase two should always be completed &lt;strong&gt;before submitting the code for review&lt;/strong&gt; or delivering it to customers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make It Work
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Prioritizing Functionality&lt;/strong&gt;: Ensuring the program works as intended. This might involve hardcoded values, inefficiencies, unhandled exceptions, etc., but that's okay for now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speed Over Perfection&lt;/strong&gt;: Delivering a working solution quickly allows us to validate our approach before investing time in enhancing it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Understanding the Problem&lt;/strong&gt;: Writing quick, functional code helps developers gain a better understanding of the problem's details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make It Better
&lt;/h2&gt;

&lt;p&gt;Here are some key areas to focus on:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clear Naming&lt;/strong&gt;: Assigning clear and descriptive names to variables, functions, and classes that reflect their intent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code Style&lt;/strong&gt;: Adhering to coding standards and guidelines to ensure consistency and readability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code Design&lt;/strong&gt;: Following established design principles like SOLID to enhance code maintainability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture&lt;/strong&gt;: Revisiting the architecture decisions to ensure they suit the specific feature requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testing&lt;/strong&gt;: Writing automated tests of the appropriate type (unit, integration, etc.) to ensure the code behaves as expected and prevents new updates from breaking existing functionality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Logging&lt;/strong&gt;: Adding meaningful logs to improve visibility and aid debugging.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Error Handling&lt;/strong&gt;: Ensuring exceptions are handled correctly and gracefully to maintain stability and reliability.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Comments&lt;/strong&gt;: Adding comments where necessary to clarify non-obvious logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance&lt;/strong&gt;: Ensuring that the code is efficient and scalable as needed.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;"Make it work, then make it better" encourages developers to focus on solving problems pragmatically before striving for perfection. Write the messy first draft, but don't forget to come back and polish it into a masterpiece!&lt;/p&gt;

&lt;p&gt;It's very common for the second phase to be ignored altogether, and this is exactly how technical debt accumulates. Always make sure to apply the second phase when building software, unless it's a throwaway prototype. This &lt;strong&gt;at least&lt;/strong&gt; ensures that the software quality will not degrade over time, which in my experience is an achievement in itself.&lt;/p&gt;

&lt;p&gt;It's worth noting that &lt;strong&gt;this principle isn't limited to programming;&lt;/strong&gt; you can apply it to many areas of life. In fact, I used this approach while writing this blog! Here's how it went:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I wrote a quick draft. (phase 1)&lt;/li&gt;
&lt;li&gt;I used chatGPT to do some enhancements and rephrasings. (phase 2)&lt;/li&gt;
&lt;li&gt;I made several iterations on chatGPT's output to make it simpler and more relevant. (phase 2)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>softwareengineering</category>
      <category>cleancode</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Fixing VS Code Go to Definition Issue on Linux Mint</title>
      <dc:creator>Mark Adel</dc:creator>
      <pubDate>Sat, 19 Oct 2024 13:57:45 +0000</pubDate>
      <link>https://dev.to/markadel/fixing-vs-code-go-to-definition-issue-on-linux-mint-2j24</link>
      <guid>https://dev.to/markadel/fixing-vs-code-go-to-definition-issue-on-linux-mint-2j24</guid>
      <description>&lt;p&gt;After switching from Linux Ubuntu to Mint (Cinnamon) on my work laptop, I discovered that the Go to Definition functionality in Visual Studio Code, which uses &lt;code&gt;Alt+Click&lt;/code&gt;, had stopped working.&lt;/p&gt;

&lt;p&gt;This issue had been driving me crazy for weeks until I finally fixed it.&lt;/p&gt;

&lt;p&gt;The problem was that Mint, by default, uses &lt;code&gt;Alt+Click&lt;/code&gt; to move and resize windows, which was overriding and conflicting with VS Code’s Go to Definition functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;In Mint’s Windows Settings, change the “Special key to move and resize windows” from &lt;code&gt;&amp;lt;Alt&amp;gt;&lt;/code&gt; to &lt;code&gt;&amp;lt;Super&amp;gt;&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>vscode</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Logging Done Right</title>
      <dc:creator>Mark Adel</dc:creator>
      <pubDate>Sun, 09 Jun 2024 02:51:30 +0000</pubDate>
      <link>https://dev.to/markadel/logging-done-right-1nnm</link>
      <guid>https://dev.to/markadel/logging-done-right-1nnm</guid>
      <description>&lt;p&gt;Writing effective log messages is crucial for the overall observability of your application. In this guide, we are going to focus mainly on what to log, and how to write effective log messages.&lt;/p&gt;

&lt;p&gt;The code examples are written in JavaScript for its simple syntax, but these guidelines are applicable to any programming language.&lt;/p&gt;

&lt;p&gt;Let's get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  1. All log messages should start with the class and function name
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[MyClass.myFunction] The log message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Reason&lt;/strong&gt;: Quickly identifying where the log message comes from. It also adds uniqueness to the logs, ensuring that logs are not duplicated.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Add logs in all exception handling blocks
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// code that might throw an error&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[OrderService.placeOrder] Order placement failed: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Reason&lt;/strong&gt;: Very crucial for troubleshooting.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Include context if useful
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[PaymentGateway.processPayment] Payment failed for UserID: 123, OrderID: 456, Error: Insufficient funds&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Reason&lt;/strong&gt;: Context helps you understand the conditions under which the log was generated, aiding in replicating and fixing issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Add logs for auditing
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[OrderService.placeOrder] Order ID: 12345 placed successfully&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Reason&lt;/strong&gt;: Audit logs have many benefits, such as reconstructing the timeline of a system outage or a security breach, and also being able to detect them while happening.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Don't include large log messages if not necessary
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Bad:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[SomeOrdersJob.processOrders] Finished processing orders chunk, index: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;chunkIndex&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;orders: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Good:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[SomeOrdersJob.processOrders] Finished processing orders chunk, index: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;chunkIndex&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;orders length: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Reason&lt;/strong&gt;: Large log messages reduce readability, consume more space, and might introduce performance implications.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Don't log sensitive data
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Bad, too bad:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[AuthService.login] User login attempt failed, login data: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loginData&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[PaymentGateway.registerPaymentCard] Card registration failed, card data: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cardData&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Good:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[AuthService.login] User login attempt failed, username: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;loginData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Reason&lt;/strong&gt;: Logging sensitive data can create security risks and violate privacy regulations such as GDPR. &lt;strong&gt;Always sanitize data before logging.&lt;/strong&gt; It's very likely to overlook sanitizing your data when logging HTTP requests and responses, for example.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Use the correct log level
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[OrderService.placeOrder] Inside placeOrder function&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[OrderService.placeOrder] Order ID: 12345 placed successfully&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[AuthService.login] User login attempt failed, username: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;loginData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[OrderService.placeOrder] Order placement failed: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[Database.connect] Unable to connect to the database: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Reason&lt;/strong&gt;: Using log levels has many benefits, such as filtering logs based on their level, taking specific actions such as sending an alert for high-severity logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Timestamp your logs
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[2024-06-08T12:00:00Z] [OrderService.placeOrder] Order ID: 12345 placed successfully&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Reason&lt;/strong&gt;: Timestamps provide a chronological order to logs, making it easier to track and understand the sequence of actions. They also allow you to troubleshoot issues that happened at specific times.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Avoid logging in loops or recursive functions if not necessary
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Bad:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[InvoiceCalculator.calculate] Calculating item: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// calculation logic&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Good:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[InvoiceCalculator.calculate] Calculation started&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// calculation logic&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[InvoiceCalculator.calculate] Calculation finished&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Reason&lt;/strong&gt;: Excessive log entries make it harder to find relevant information, consume more space, and can lead to performance issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Log the start and end of long-running operations
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[DataImportService.importData] Data import started&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// long-running operation&lt;/span&gt;

&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[DataImportService.importData] Data import completed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Reason&lt;/strong&gt;: It helps you monitor the progress and duration of these operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Ensure log messages are concise and clear
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Bad:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[OrderService.placeOrder] The order placement function has successfully completed processing the order with ID 12345&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Good:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[OrderService.placeOrder] Order ID: 12345 placed successfully&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Reason&lt;/strong&gt;: Concise and clear log messages are easier to read and understand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I hope you found this guide helpful. Please feel free to suggest other practices that you think are important, and I will be happy to include them.&lt;/p&gt;

&lt;h2&gt;
  
  
  References and Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.dataset.com/blog/the-10-commandments-of-logging/"&gt;https://www.dataset.com/blog/the-10-commandments-of-logging/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@squarecog/logging-101-d74ff92f8c91"&gt;https://medium.com/@squarecog/logging-101-d74ff92f8c91&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.datadoghq.com/knowledge-center/audit-logging/"&gt;https://www.datadoghq.com/knowledge-center/audit-logging/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>programming</category>
      <category>tutorial</category>
      <category>backend</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Disaster Recovery: Better Safe Than Sorry</title>
      <dc:creator>Mark Adel</dc:creator>
      <pubDate>Fri, 26 Apr 2024 17:20:58 +0000</pubDate>
      <link>https://dev.to/markadel/disaster-recovery-better-safe-than-sorry-19p6</link>
      <guid>https://dev.to/markadel/disaster-recovery-better-safe-than-sorry-19p6</guid>
      <description>&lt;p&gt;Do you work for a tech startup? Does your startup have a disaster recovery plan? If not, read on.&lt;/p&gt;

&lt;p&gt;What is disaster recovery?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disaster recovery (DR) is an organization's ability to restore access and functionality to IT infrastructure after a disaster event, whether natural or caused by human action (or error). DR is considered a subset of business continuity, explicitly focusing on ensuring that the IT systems that support critical business functions are operational as soon as possible after a disruptive event occurs. &lt;a href="https://cloud.google.com/learn/what-is-disaster-recovery"&gt;1&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As someone who has experience working in fast-paced tech startups. I know that focusing on rapid growth can sometimes lead to overlooking critical operational aspects, such as having a disaster recovery plan (DRP).&lt;/p&gt;

&lt;p&gt;There are several reasons why a startup might ignore or overlook having a DRP, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Constantly prioritizing shipping features that lead to immediate gains.&lt;/li&gt;
&lt;li&gt;Lack of awareness about the consequences of not having one.&lt;/li&gt;
&lt;li&gt;The startup hasn't yet faced its first major disaster, leading to a false sense of security.&lt;/li&gt;
&lt;li&gt;Resistance to investing time and resources into planning for hypothetical scenarios.&lt;/li&gt;
&lt;li&gt;Lack of relevant expertise or guidance, which leads to procrastination in planning and implementing disaster recovery.&lt;/li&gt;
&lt;li&gt;The misconception that disaster recovery is only necessary for large corporations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of the reasons above justify not implementing disaster recovery. A simple human error, such as an employee executing a dangerous command, can kill the startup and everything it has been working for over its lifetime. Disaster recovery is an essential aspect of risk management in any organization, regardless of its size.&lt;/p&gt;

&lt;p&gt;If you are part of a startup that still doesn't have a DRP, it's important to speak up. Share your concerns, and educate your team and management about the importance of disaster recovery. Your efforts will be greatly appreciated when the company faces a disruptive event and can recover from it with minimal damage.&lt;/p&gt;

&lt;p&gt;Essential steps for implementing disaster recovery:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Evaluate potential threats and identify the most critical system components that require protection against disasters.&lt;/li&gt;
&lt;li&gt;Create a plan that outlines the following:

&lt;ul&gt;
&lt;li&gt;The necessary actions to protect these components, such as preventative measures and data backups.&lt;/li&gt;
&lt;li&gt;The procedures for responding to different types of disasters.&lt;/li&gt;
&lt;li&gt;The individuals responsible for executing these procedures.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Ensure that everyone understands their role during a disaster and knows how to execute the recovery procedures.&lt;/li&gt;
&lt;li&gt;Regularly update your backups to minimize the damage caused by data loss during disasters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Please note that these are the minimum steps. Depending on the nature of your business, you may need to include additional steps or details in your DRP.&lt;/p&gt;

&lt;p&gt;Remember, a well-implemented DRP can be the difference between minor damage and a catastrophic failure for your organization.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;I hope this post has been an eye-opener. The importance of disaster recovery cannot be overstated. It's not just about surviving a disaster, but also about maintaining trust and ensuring business continuity. A minor effort now can prevent a major crisis later. It's always better to be safe than sorry. Good luck!&lt;/p&gt;

</description>
      <category>security</category>
      <category>softwareengineering</category>
      <category>data</category>
      <category>programming</category>
    </item>
    <item>
      <title>Diagnose Before You Prescribe</title>
      <dc:creator>Mark Adel</dc:creator>
      <pubDate>Wed, 24 Apr 2024 03:35:05 +0000</pubDate>
      <link>https://dev.to/markadel/diagnose-before-you-prescribe-3p81</link>
      <guid>https://dev.to/markadel/diagnose-before-you-prescribe-3p81</guid>
      <description>&lt;p&gt;Imagine you go to the doctor feeling unwell, and without examining you, they immediately start writing a prescription. This is exactly what some software engineers occasionally do.&lt;/p&gt;

&lt;p&gt;When we are faced with an issue in a particular component of our system, especially if it's a performance issue that is challenging to debug, it's not uncommon to find ourselves trying to think of ways to fix it. We may even feel urged to redesign the entire component in a way that we believe is more efficient than the current one, and spare ourselves the burden of debugging.&lt;/p&gt;

&lt;p&gt;This will always be the wrong approach. Rushing to solutions without fully understanding the problem can lead to wasted time because there is a high chance that the solution doesn't address the problem. Even if the solution works, you will never be sure whether it really solves the problem or if it's just a coincidence.&lt;/p&gt;

&lt;p&gt;Therefore, it's important to resist the urge to jump to solutions and instead, invest time in thoroughly understanding the problem. When faced with a performance issue, the first thing you should do is try to understand the root cause. This involves debugging, examining the logs and relevant performance metrics, etc. Only then should you start looking for a solution, which might be a very simple update that requires minimal effort.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>softwareengineering</category>
      <category>performance</category>
    </item>
    <item>
      <title>Backend Code Review Checklist</title>
      <dc:creator>Mark Adel</dc:creator>
      <pubDate>Sat, 30 Mar 2024 21:39:50 +0000</pubDate>
      <link>https://dev.to/markadel/backend-code-review-checklist-280h</link>
      <guid>https://dev.to/markadel/backend-code-review-checklist-280h</guid>
      <description>&lt;p&gt;I have put together this checklist, which I believe will be applicable to most backend code reviews.&lt;/p&gt;

&lt;p&gt;Some of these checks, such as Code Style, should ideally be enforced and detected in the CI pipeline. However, I have included them here for the sake of completeness.&lt;/p&gt;

&lt;p&gt;You can use this checklist as a starting point and customize it to suit your specific needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Style
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Verify that the code adheres to the agreed-upon coding style guidelines.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Code Maintainability
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Verify that the code adheres to the clean code principles (or any other agreed-upon principles).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Verify that the code fulfills the specified requirements.&lt;/li&gt;
&lt;li&gt;Verify that new code doesn't break any existing functionality.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  API Design
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Verify that any new APIs adhere to the agreed-upon API design guidelines.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Documentation and Comments
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Verify that complex logic or non-obvious decisions are covered by clear comments.&lt;/li&gt;
&lt;li&gt;Verify that any required internal or external code documentation is provided, depending on your agreed-upon documentation processes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Error Handling
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Verify that exceptions are handled correctly and that error messages are informative.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Security
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Verify that inputs are validated properly.&lt;/li&gt;
&lt;li&gt;Verify that sensitive data (passwords, tokens) are securely stored and aren't leaked to logs.&lt;/li&gt;
&lt;li&gt;Examine the code for potential security vulnerabilities, such as SQL injection or authentication issues.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Dependencies
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Verify that dependencies are up-to-date and don't have known security vulnerabilities.&lt;/li&gt;
&lt;li&gt;Verify that any breaking changes are handled when updating dependencies.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Logging
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Verify that critical places in the code are covered by logs that are useful for debugging.&lt;/li&gt;
&lt;li&gt;Verify that logging adheres to the agreed-upon logging guidelines.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Verify that the code is covered by the appropriate types of automated tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Performance
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Evaluate the code for performance issues (memory, CPU, network).&lt;/li&gt;
&lt;li&gt;Verify that database queries are optimized.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Version Control
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Verify that the agreed-upon version control workflow and practices are followed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Spelling
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Verify that the spelling is correct, as this makes the code more searchable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;I hope you found this checklist useful. Please feel free to suggest additional checks that you think are necessary.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>backend</category>
      <category>softwareengineering</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Useful Global Git Configurations</title>
      <dc:creator>Mark Adel</dc:creator>
      <pubDate>Wed, 06 Mar 2024 13:31:00 +0000</pubDate>
      <link>https://dev.to/markadel/useful-global-git-configurations-2ce2</link>
      <guid>https://dev.to/markadel/useful-global-git-configurations-2ce2</guid>
      <description>&lt;p&gt;In this post, I'm going to share with you six global Git configurations that I use and find incredibly useful.&lt;/p&gt;

&lt;p&gt;Global Git configurations are settings that apply to all your local repositories. They are stored in a file called &lt;code&gt;.gitconfig&lt;/code&gt; in your home directory. You can view and edit this file, or you can use the &lt;code&gt;git config&lt;/code&gt; command to modify it from the terminal.&lt;/p&gt;

&lt;p&gt;To see the global configurations through the terminal, you can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; &lt;span class="nt"&gt;--list&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's get to it!&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;git config --global init.defaultBranch main&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This sets the default branch name to &lt;code&gt;main&lt;/code&gt; instead of &lt;code&gt;master&lt;/code&gt; when creating a new repository using &lt;code&gt;git init&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;code&gt;git config --global help.autoCorrect prompt&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;By default, Git checks for spelling errors and suggests corrections, but it doesn’t automatically apply them. After configuring this setting, Git will prompt you to run the suggested correction.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git pusj
WARNING: You called a Git command named 'pusj', which does not exist.
Run 'push' instead [y/N]?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To allow git to run corrections automatically, you can use &lt;code&gt;help.autoCorrect 1&lt;/code&gt; (runs after 0.1 seconds), &lt;code&gt;help.autoCorrect 10&lt;/code&gt; (runs after 10 seconds), or &lt;code&gt;help.autoCorrect immediate&lt;/code&gt; (runs immediately).&lt;/p&gt;

&lt;p&gt;I prefer using &lt;code&gt;help.autoCorrect prompt&lt;/code&gt; to ensure that I will run the command that I intend to.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;code&gt;git config --global branch.sort -committerdate&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This makes &lt;code&gt;git branch&lt;/code&gt; sort the branches by the most recently used branches instead of alphabetically. This helps you to quickly find the branches that you are working on.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;code&gt;git config --global fetch.prune true&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;When you run &lt;code&gt;git fetch&lt;/code&gt;, any branches that have been deleted on the remote repository will automatically be deleted from your local repository.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. &lt;code&gt;git config --global log.date iso&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;When you run &lt;code&gt;git log&lt;/code&gt;, dates will be displayed in ISO format for better readability.&lt;/p&gt;

&lt;p&gt;For example, &lt;code&gt;2024-03-16 05:30:02&lt;/code&gt; instead of &lt;code&gt;Sun Mar 16 05:30:02 2024&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. &lt;code&gt;git config --global push.autoSetupRemote true&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This makes &lt;code&gt;git push&lt;/code&gt; automatically set up the remote branch. Otherwise, you'd need to do it manually like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git push &lt;span class="nt"&gt;--set-upstream&lt;/span&gt; origin &amp;lt;branch-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I hope you found these configurations helpful. Please feel free to share yours!&lt;/p&gt;

&lt;p&gt;You can find more on &lt;a href="https://git-scm.com/docs/git-config"&gt;Git documentation&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>git</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>PostgreSQL Isolation Levels and Locking Summary</title>
      <dc:creator>Mark Adel</dc:creator>
      <pubDate>Fri, 16 Feb 2024 19:54:27 +0000</pubDate>
      <link>https://dev.to/markadel/postgresql-isolation-levels-and-locking-summary-9ac</link>
      <guid>https://dev.to/markadel/postgresql-isolation-levels-and-locking-summary-9ac</guid>
      <description>&lt;p&gt;I initially prepared this summary for myself. However, I thought other people would find it useful, so I refined it as necessary and decided to share it.&lt;/p&gt;

&lt;p&gt;Please note that this serves only as a summary or even a cheat sheet. If you are new to these topics, it's advisable to do further reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read Phenomena
&lt;/h2&gt;

&lt;p&gt;Read phenomena are potential issues (anomalies) that can occur in a database when multiple transactions are executed concurrently.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dirty Read&lt;/strong&gt;: A transaction reads data written by another concurrent uncommitted transaction.

&lt;ul&gt;
&lt;li&gt;It's like reading a draft that someone else is still writing.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nonrepeatable Read&lt;/strong&gt;: A transaction re-reads columns and finds that their values have been changed due to another concurrently committed transaction.

&lt;ul&gt;
&lt;li&gt;It's like re-reading a page of a book and finding that the words have changed since your first read.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phantom Read&lt;/strong&gt;: A transaction re-reads a set of rows satisfying a specific criteria and finds newly added or removed rows due to another concurrently committed transaction.

&lt;ul&gt;
&lt;li&gt;It's like counting a group of people, then finding the number changes when you count again because new people have left or joined.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serialization Anomaly&lt;/strong&gt;: The state of data resulting from executing transactions concurrently differs from the state resulting from running them one at a time.

&lt;ul&gt;
&lt;li&gt;It's like when you are baking a cake, it might taste differently if you added all the ingredients at the same time than if you added them one at a time in a specific order.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Isolation Levels
&lt;/h2&gt;

&lt;p&gt;Isolation levels determine how transactions influence each other.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;Read Uncommitted&lt;/code&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dirty Read&lt;/strong&gt;: Possible, but not in PostgreSQL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nonrepeatable Read&lt;/strong&gt;: Possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phantom Read&lt;/strong&gt;: Possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serialization Anomaly&lt;/strong&gt;: Possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;READ UNCOMMITTED&lt;/code&gt; behaves the same as &lt;code&gt;READ COMMITTED&lt;/code&gt; in PostgreSQL.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;Read Committed&lt;/code&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dirty Read&lt;/strong&gt;: Not possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nonrepeatable Read&lt;/strong&gt;: Possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phantom Read&lt;/strong&gt;: Possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serialization Anomaly&lt;/strong&gt;: Possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;READ COMMITTED&lt;/code&gt; is the default isolation level in PostgreSQL.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;Repeatable Read&lt;/code&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dirty Read&lt;/strong&gt;: Not possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nonrepeatable Read&lt;/strong&gt;: Not possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phantom Read&lt;/strong&gt;: Possible, but not in PostgreSQL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serialization Anomaly&lt;/strong&gt;: Possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;Serializable&lt;/code&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dirty Read&lt;/strong&gt;: Not possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nonrepeatable Read&lt;/strong&gt;: Not possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phantom Read&lt;/strong&gt;: Not possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serialization Anomaly&lt;/strong&gt;: Not possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can set the transaction isolation level in PostgreSQL using the following syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt; &lt;span class="n"&gt;TRANSACTION&lt;/span&gt; &lt;span class="k"&gt;ISOLATION&lt;/span&gt; &lt;span class="k"&gt;LEVEL&lt;/span&gt; &lt;span class="k"&gt;READ&lt;/span&gt; &lt;span class="k"&gt;COMMITTED&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Your queries&lt;/span&gt;
&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Locking (Row-level locking)
&lt;/h2&gt;

&lt;p&gt;Locking is a mechanism that prevents concurrent transactions from interfering with each other when accessing or modifying the same data.&lt;/p&gt;

&lt;p&gt;One of the primary use cases for locking is to prevent anomalies such as the &lt;strong&gt;Lost Update&lt;/strong&gt;, which can occur in lower isolation levels such as &lt;code&gt;READ COMMITTED&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Lost Update&lt;/strong&gt; anomaly occurs when two transactions attempt to update the same data concurrently, and the changes made by one transaction are overwritten by the other, resulting in the loss of the first transaction updates.&lt;/p&gt;

&lt;p&gt;There are two common approaches to locking: pessimistic locking and optimistic locking.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pessimistic Locking
&lt;/h3&gt;

&lt;p&gt;Pessimistic locking assumes that conflicts will happen and so it prevents them by acquiring locks on the data. This means that a transaction will block other concurrent transactions from accessing the same data until it completes. There are two types of pessimistic locks: Shared lock and Exclusive lock.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shared Lock:&lt;/strong&gt; A shared lock allows multiple transactions to read the same data concurrently but prevents them from updating it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exclusive Lock:&lt;/strong&gt; An exclusive lock restricts access to data to a single transaction, preventing other transactions from reading or updating it.&lt;/p&gt;

&lt;p&gt;Pessimistic locking is often used when the likelihood of conflicts is high.&lt;/p&gt;

&lt;p&gt;When selecting rows in postgreSQL, you can use &lt;code&gt;FOR UPDATE&lt;/code&gt;, &lt;code&gt;FOR NO KEY UPDATE&lt;/code&gt;, &lt;code&gt;FOR SHARE&lt;/code&gt; and &lt;code&gt;FOR KEY SHARE&lt;/code&gt; locking modes to explicitly lock the selected rows. Each mode provides a different level of restriction in terms of which operations other concurrent transactions are allowed to do.&lt;/p&gt;

&lt;p&gt;Here is an example of using &lt;code&gt;SELECT FOR UPDATE&lt;/code&gt;, which creates an exclusive lock.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Other transactions trying to read or update the same row will be blocked until this transaction completes&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please note that normal &lt;code&gt;SELECT&lt;/code&gt; statements under an isolation level like &lt;code&gt;READ COMMITTED&lt;/code&gt; will not be blocked by a &lt;code&gt;SELECT FOR UPDATE&lt;/code&gt; or any other locking mode, as normal &lt;code&gt;SELECT&lt;/code&gt; statements don't acquire any locks on the rows they read, so they don't cause any conflicts with other transactions.&lt;/p&gt;

&lt;h4&gt;
  
  
  Lock modes ordered by restrictiveness from highest to lowest:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;SELECT .. FOR UPDATE&lt;/code&gt; (exclusive lock)&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Blocks concurrent transactions on the same rows from performing &lt;code&gt;SELECT FOR UPDATE&lt;/code&gt;, &lt;code&gt;SELECT FOR NO KEY UPDATE&lt;/code&gt;, &lt;code&gt;SELECT FOR SHARE&lt;/code&gt;, &lt;code&gt;SELECT FOR KEY SHARE&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, and &lt;code&gt;DELETE&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;This is the mode implicitly used for &lt;code&gt;DELETE&lt;/code&gt; commands, and &lt;code&gt;UPDATE&lt;/code&gt; commands on columns with unique indexes.&lt;/li&gt;
&lt;li&gt;Use when you want to prevent other transactions from modifying or acquiring any type of lock on the selected rows.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;SELECT .. FOR NO KEY UPDATE&lt;/code&gt; (shared lock)&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Blocks concurrent transactions on the same rows from performing &lt;code&gt;SELECT FOR UPDATE&lt;/code&gt;, &lt;code&gt;SELECT FOR NO KEY UPDATE&lt;/code&gt;, &lt;code&gt;SELECT FOR SHARE&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;, and any &lt;code&gt;UPDATE&lt;/code&gt; that changes key values (such as primary keys).&lt;/li&gt;
&lt;li&gt;This is the mode implicitly used for &lt;code&gt;UPDATE&lt;/code&gt; commands that don't acquire a &lt;code&gt;FOR UPDATE&lt;/code&gt; lock.&lt;/li&gt;
&lt;li&gt;Use when you want to lock selected rows against concurrent updates that change key values and from exclusive locks, while allowing other transactions to acquire &lt;code&gt;SELECT FOR KEY SHARE&lt;/code&gt; locks.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;SELECT .. FOR SHARE&lt;/code&gt; (shared lock)&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Blocks concurrent transactions on the same rows from performing &lt;code&gt;SELECT FOR UPDATE&lt;/code&gt;, &lt;code&gt;SELECT FOR NO KEY UPDATE&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, and &lt;code&gt;DELETE&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Use when you want to lock selected rows against all concurrent updates and from exclusive locks, while allowing other transactions to acquire &lt;code&gt;SELECT FOR SHARE&lt;/code&gt; or &lt;code&gt;SELECT FOR KEY SHARE&lt;/code&gt; locks.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;SELECT .. FOR KEY SHARE&lt;/code&gt; (shared lock)&lt;/strong&gt; 

&lt;ul&gt;
&lt;li&gt;Blocks concurrent transactions on the same rows from performing &lt;code&gt;SELECT FOR UPDATE&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;, and any &lt;code&gt;UPDATE&lt;/code&gt; that changes key values (such as primary keys).&lt;/li&gt;
&lt;li&gt;Use when you want to lock selected rows against concurrent updates that change key values and from exclusive locks, while allowing other transactions to acquire any type of shared lock.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Deadlocks
&lt;/h4&gt;

&lt;p&gt;A deadlock happens when two or more transactions are waiting for each other to release locks. PostgreSQL periodically checks for deadlocks and handles them.&lt;/p&gt;

&lt;p&gt;If a lock isn't released within the configurable parameter &lt;code&gt;deadlock_timeout&lt;/code&gt; (default is 1 second), PostgreSQL will trigger the deadlock detection algorithm and if a deadlock was found, one of the involved transactions will be rolled back.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optimistic Locking
&lt;/h3&gt;

&lt;p&gt;Optimistic locking doesn't assume that conflicts will happen, so it allows all transactions to run concurrently. If a conflict is detected at commit time (two concurrent transactions have modified the same data), one transaction will proceed and the other will be rolled back and must retry.&lt;/p&gt;

&lt;p&gt;Optimistic locking is often used when the likelihood of conflicts is low.&lt;/p&gt;

&lt;p&gt;In PostgreSQL, you can implement optimistic locking using a versioning field in your tables. Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="nb"&gt;NUMERIC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;version&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When updating a row, increment the version and include it in the WHERE clause:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; 
    &lt;span class="n"&gt;products&lt;/span&gt; 
&lt;span class="k"&gt;SET&lt;/span&gt; 
    &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;version&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;RETURNING&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If no rows are updated, it means another transaction has already updated the row and your transaction should retry.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;I hope you found this summary helpful. Please let me know if you discovered any errors or have any suggestions on what to add or update, while still keeping it concise.&lt;/p&gt;

&lt;h1&gt;
  
  
  Further Reading
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://www.postgresql.org/docs/current/mvcc.html"&gt;PostgreSQL documentation&lt;/a&gt;&lt;/p&gt;

</description>
      <category>postgressql</category>
      <category>database</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>To-Do Lists for the Rescue</title>
      <dc:creator>Mark Adel</dc:creator>
      <pubDate>Thu, 18 Jan 2024 19:13:02 +0000</pubDate>
      <link>https://dev.to/markadel/to-do-lists-for-the-rescue-1epn</link>
      <guid>https://dev.to/markadel/to-do-lists-for-the-rescue-1epn</guid>
      <description>&lt;p&gt;In this article, I'm going to share my personal experience about the benefits of using to-do lists and share some tips that I developed over time which helped me use my to-do lists effectively.&lt;/p&gt;

&lt;p&gt;I'm aware that there are countless resources over the internet on topics related to to-do lists, and time management in general. However, I believe that sharing personal experiences always adds new perspectives. I didn't do any research before or during writing this article to ensure that everything shared here is based solely on my experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why should we use to-do lists?
&lt;/h2&gt;

&lt;p&gt;My life before using to-do lists has been very messy. To-do lists helped me bring more order to my life, be more productive, overcome procrastination, start achieving my goals, and more.&lt;/p&gt;

&lt;p&gt;Let's discuss the benefits of using to-do lists and why relying on our brains is not a good idea.&lt;/p&gt;

&lt;h3&gt;
  
  
  Our brains are unreliable
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Limited memory
&lt;/h4&gt;

&lt;p&gt;You can barely use your memory to remember today's tasks, and even for that it sometimes fails you and forgets things. How about if you want to keep track of short or long term tasks? This list can grow long, and relying on your memory becomes impractical.&lt;/p&gt;

&lt;p&gt;By using to-do lists, there is no need to use your memory.&lt;/p&gt;

&lt;h4&gt;
  
  
  Overwhelm
&lt;/h4&gt;

&lt;p&gt;Even if you manage to keep all the tasks in your memory, you will probably get overwhelmed by their amount, the size of each task, and the lack of clarity regarding their priorities or the order in which they should be executed. So you likely end up doing nothing.&lt;/p&gt;

&lt;p&gt;By using to-do lists, you can organize the tasks in multiple lists, set their priorities and their order, and break down big tasks into smaller ones. Doing this eliminates the possibility of getting overwhelmed.&lt;/p&gt;

&lt;h3&gt;
  
  
  They motivate you to get things done
&lt;/h3&gt;

&lt;p&gt;After filling out my daily to-do list, I find that I become motivated to start working on the tasks and getting them done. This results in less overall procrastination and wasted time.&lt;/p&gt;

&lt;h3&gt;
  
  
  They add structure to your day
&lt;/h3&gt;

&lt;p&gt;Without using a to-do list, your day becomes subject to being very messy, since nothing stops you from doing anything at any time. So, you either end up wasting a lot of your time, like scrolling indefinitely in social media apps, playing video games, etc., or you find something to do that you think is important, just to not feel guilty for wasting your entire day.&lt;/p&gt;

&lt;p&gt;A to-do list adds structure to your day. You are always able to answer the question, "What should I be doing right now?" All you need to do is consult your to-do list, which should have today's tasks ordered by their priority. While this doesn't stop you from wasting time, wasting time now becomes a conscious choice, rather than a result of an unplanned day, making you less likely to waste significant amounts of time.&lt;/p&gt;

&lt;h3&gt;
  
  
  They encourage you to set goals
&lt;/h3&gt;

&lt;p&gt;I didn't have my short or long-term goals written anywhere before. Using to-do lists encouraged me to start documenting my goals and breaking them down into actions. I ensure that my daily to-do list includes at least one or two tasks that contribute to my goals.&lt;/p&gt;

&lt;h3&gt;
  
  
  They help you set priorities
&lt;/h3&gt;

&lt;p&gt;By having all the tasks clearly written in front of you, it becomes more feasible to prioritize them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: Not all tasks that seem important are important. Always ask yourself whether these tasks, especially big ones, contribute to one of your current goals. If not, they are probably not due now, and there are more important ones that directly impact your goals.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to structure to-do lists
&lt;/h2&gt;

&lt;p&gt;There is no right, wrong, or even recommended way to structure your to-do lists. You can structure them in any way you like, as long as they are effective and achieve the benefits we discussed in the previous section.&lt;/p&gt;

&lt;p&gt;I'm going to share two of the lists that I use, which I believe everyone can make use of, as these are the ones we use in our day-to-day lives: "Daily to-do list" and "Pool of to-dos".&lt;/p&gt;

&lt;p&gt;Other lists that you may consider having, just to give you an idea: Goals for Q1 2024, Shopping list, Places to visit, Future tasks (tasks not immediately necessary but you don't want to forget), and more.&lt;/p&gt;

&lt;p&gt;Let's explore the Daily to-do list and the Pool of to-dos.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Daily to-do list
&lt;/h3&gt;

&lt;p&gt;We are all familiar with this one. This list contains tasks to be completed within a specific day. It should be prepared at the start of the day and updated as necessary throughout the day.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Pool of to-dos
&lt;/h3&gt;

&lt;p&gt;This list is used for tasks that are ready to be executed, but can't all be squeezed into today's to-dos. It serves as a pool of tasks waiting for their turn to be moved to the Daily to-do list when the time is right.&lt;/p&gt;

&lt;p&gt;Whenever a task of this nature comes up, drop it to your pool of to-dos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tips for using to-do lists effectively
&lt;/h2&gt;

&lt;p&gt;Following are some tips that I developed over time for making the most out of the to-do lists. These tips specifically apply to the Daily to-do list and the Pool of to-dos.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep Pool of to-dos and Daily to-do list in the same place
&lt;/h3&gt;

&lt;p&gt;These two lists are often used together, so it makes sense to keep them close. Ideally, you should be able to drag and drop tasks between them, as this is something you will do all the time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Your to-do lists should be easily accessible
&lt;/h3&gt;

&lt;p&gt;Ensure that the place where you keep your to-do lists is no more than two clicks away on any device that you use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep tasks small
&lt;/h3&gt;

&lt;p&gt;Ensure that each task takes no longer than two hours to complete unless there are specific exceptions.&lt;/p&gt;

&lt;p&gt;Big tasks should be broken down into smaller, more manageable ones. &lt;/p&gt;

&lt;p&gt;Having big tasks on your list can lead to feeling overwhelmed, which increases the likelihood to procrastinate. Additionally, keeping your tasks small allows you to work on multiple items each day.&lt;/p&gt;

&lt;p&gt;Writing this article was a big task on my daily to-do list. It wasn't necessary to complete it in one or two days, so I kept it on my daily to-do list with a note "(recurring daily until completed)". I would then work on it for an hour or two each day, then mark it as completed for this day.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prioritize your tasks
&lt;/h3&gt;

&lt;p&gt;Order your tasks based on their priority. In the case of equally important tasks, I usually start with the more annoying ones. By doing this, you ensure that you will complete the important tasks each day.&lt;/p&gt;

&lt;p&gt;Please note that it's important to prioritize tasks on your pool of to-dos too.&lt;/p&gt;

&lt;h3&gt;
  
  
  Be realistic
&lt;/h3&gt;

&lt;p&gt;When filling your Daily to-do list, avoid adding more tasks than you can complete, considering your energy and available time for the day.&lt;/p&gt;

&lt;h3&gt;
  
  
  Start with yesterday's leftovers
&lt;/h3&gt;

&lt;p&gt;If for some reason you didn't complete all of yesterday's tasks, consider starting with the uncompleted ones. This prevents uncompleted tasks from lingering on your daily to-do list for multiple days.&lt;/p&gt;

&lt;p&gt;If an uncompleted task stays in your daily to-do list for more than two days, consider moving it back to the pool of to-dos, and only move it to your daily to-do list when you are ready to complete it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ensure that tasks on your pool of to-dos do not rot
&lt;/h3&gt;

&lt;p&gt;Some tasks may stay in your pool of to-dos for many days or even weeks. To handle this problem, from time to time, I go over my pool of to-dos and try to include one or two of these tasks in my daily to-do list, regardless of their priority.&lt;/p&gt;

&lt;h3&gt;
  
  
  Don't sacrifice sleep
&lt;/h3&gt;

&lt;p&gt;Don't stay up because you still have unfinished tasks. Instead, strive to finish all the tasks on your daily to-do list before your regular bedtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set deadlines
&lt;/h3&gt;

&lt;p&gt;I don't have the need to set deadlines for my daily to-do list tasks, but you may find it beneficial. It depends on your work style and the nature of the tasks at hand.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep it clean
&lt;/h3&gt;

&lt;p&gt;I prefer to use correct language when writing the tasks, avoiding spelling or grammar mistakes. The only time I violate this is when I need to drop a task into my pool of to-dos while I'm in the middle of doing something else. I would fix the language later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Include playing too
&lt;/h3&gt;

&lt;p&gt;I like playing blitz chess (3 minutes time format). Chess used to consume a good portion of my free time. After I started using to-do lists, I decided to include chess in my daily tasks. I would add two or three tasks labeled "Play 2 chess games" to my daily to-do list. This not only acted as a motivation to complete the preceding tasks to get to each playing task, but it also helped limit the number of games I play. Previously, nothing prevented me from playing over 15 back-to-back games each time I started to play.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This concludes the end of this article. Please note that everything written is based on my own experience. What works for me might not necessarily work for you. I hope I have given you ideas on how to use your to-do lists effectively, or inspired you to start using them if you haven't already.&lt;/p&gt;

&lt;p&gt;I may update this article as I learn new things, so keep an eye out for it!&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>career</category>
    </item>
    <item>
      <title>A Practical Example of Using SQL Self Joins</title>
      <dc:creator>Mark Adel</dc:creator>
      <pubDate>Wed, 03 Jan 2024 18:33:12 +0000</pubDate>
      <link>https://dev.to/markadel/a-practical-example-of-using-sql-self-joins-ndd</link>
      <guid>https://dev.to/markadel/a-practical-example-of-using-sql-self-joins-ndd</guid>
      <description>&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;In this article, I am going to share a scenario that I recently faced at work and made use of SQL self joins.&lt;/p&gt;

&lt;p&gt;Our company operates a food ordering app. We noticed that some customers add items to their basket, and for one or more hours, do not proceed to create an order. To address this, we decided to send these customers a push notification to encourage them to complete the ordering process. And that was my task. In this article, we will focus on how to retrieve these customers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;We have a relational table called &lt;code&gt;customer_events&lt;/code&gt; where we store various events created by our app customers, such as: &lt;code&gt;'opened_app'&lt;/code&gt;, &lt;code&gt;'added_items_to_basket'&lt;/code&gt;, &lt;code&gt;'created_order'&lt;/code&gt;, &lt;code&gt;'closed_app'&lt;/code&gt;, and so on.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;While Time-Series Databases could be a more suitable solution for this type of data, given our specific scenario and scale, we decided to stick with the traditional relational database.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Following is a sample of the &lt;code&gt;customer_events&lt;/code&gt; table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;customer_id&lt;/th&gt;
&lt;th&gt;event_type&lt;/th&gt;
&lt;th&gt;event_timestamp&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;opened_app&lt;/td&gt;
&lt;td&gt;2023-01-01 11:00:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;added_items_to_basket&lt;/td&gt;
&lt;td&gt;2023-01-01 12:00:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;closed_app&lt;/td&gt;
&lt;td&gt;2023-01-01 12:10:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;added_items_to_basket&lt;/td&gt;
&lt;td&gt;2023-01-01 13:00:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;created_order&lt;/td&gt;
&lt;td&gt;2023-01-01 13:30:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;added_items_to_basket&lt;/td&gt;
&lt;td&gt;2023-01-01 15:00:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;added_items_to_basket&lt;/td&gt;
&lt;td&gt;2023-01-01 16:30:00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We have events from 5 customers. Only the &lt;code&gt;customer_id&lt;/code&gt; of &lt;code&gt;40&lt;/code&gt; added items to their basket and then created an order. The others did not, so we want to retrieve these customers to send them a push notification, which are &lt;code&gt;customer_id&lt;/code&gt; of &lt;code&gt;30&lt;/code&gt;, &lt;code&gt;50&lt;/code&gt;, and &lt;code&gt;60&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Before writing non-straightforward SQL queries, I like to use plain text to describe exactly what I want to achieve, in a way that would help me translate that into the query. Let's see what I would write for our case:&lt;/p&gt;

&lt;p&gt;"I want to retrieve customer IDs that their &lt;code&gt;event_type&lt;/code&gt; = &lt;code&gt;'added_items_to_basket'&lt;/code&gt; and their &lt;code&gt;event_timestamp&lt;/code&gt; is one or more hours ago, excluding those for whom there exists a row with the same &lt;code&gt;customer_id&lt;/code&gt; and &lt;code&gt;event_name&lt;/code&gt; = &lt;code&gt;'created_order'&lt;/code&gt; and a greater &lt;code&gt;event_timestamp&lt;/code&gt;"&lt;/p&gt;

&lt;p&gt;Let's start with the easy part of the query, which is selecting all customers who added items to their basket at least one hour ago:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;customer_id&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
    &lt;span class="n"&gt;customer_events&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
    &lt;span class="n"&gt;event_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'added_items_to_basket'&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EPOCH&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;event_timestamp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's update the query to exclude those customers who created an order after they added items to the basket. We can do this using self joins:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
    &lt;span class="n"&gt;customer_events&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt;
    &lt;span class="n"&gt;customer_events&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'created_order'&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event_timestamp&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event_timestamp&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'added_items_to_basket'&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EPOCH&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event_timestamp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To the untrained eye, this query might look intimidating, but it's actually quite simple. Let's break it down.&lt;/p&gt;

&lt;p&gt;We performed a &lt;code&gt;LEFT JOIN&lt;/code&gt; on the &lt;code&gt;customer_events&lt;/code&gt; table (a) with itself (b). This retrieves all rows from the left table (a) along with their matching rows from the right table (b) based on the specified join conditions. In the right side, all columns will be &lt;code&gt;NULL&lt;/code&gt; if there are no subsequent &lt;code&gt;'created_order'&lt;/code&gt; event for the customer on the left side, and will have values otherwise. We use the &lt;code&gt;b.customer_id IS NULL&lt;/code&gt; condition to only get customers where there are no subsequent &lt;code&gt;'created_order'&lt;/code&gt; event.&lt;/p&gt;

&lt;p&gt;Query Output:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;customer_id&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you are not sure what the output of the self join looks like before filtering using &lt;code&gt;b.customer_id IS NULL&lt;/code&gt;, you may run &lt;a href="https://www.db-fiddle.com/f/wtS6yTrzRXp9dbfgfRBQcn/1"&gt;this&lt;/a&gt; example I prepared on &lt;a href="https://www.db-fiddle.com/"&gt;db-fiddle&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It is worth noting that this is a simplified version of the actual query. There are other cases that we need to handle, such as ensuring that the order belongs to the specified basket, or only taking the last event into account. However, this is beyond the scope of this article.&lt;/p&gt;

&lt;p&gt;Now, I want you to go back and read the plain text we wrote for this query, and then check the query again. You will find that it does exactly what we described.&lt;/p&gt;

&lt;h2&gt;
  
  
  Another Approach
&lt;/h2&gt;

&lt;p&gt;There is an alternative way we can write this query to achieve the same result, using sub-queries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
    &lt;span class="n"&gt;customer_events&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'added_items_to_basket'&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EPOCH&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event_timestamp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customer_events&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
        &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;
        &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'created_order'&lt;/span&gt;
        &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event_timestamp&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event_timestamp&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which approach to use? We have two factors to consider: readability and performance.&lt;/p&gt;

&lt;p&gt;In terms of readability, it is a personal preference. For me, I find both equally readable.&lt;/p&gt;

&lt;p&gt;In terms of performance, it depends on many factors. It is always advisable to check the execution plans of your queries.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Whenever you find yourself in a situation where you want to exclude certain rows from your result based on conditions that involve other rows in the same table, SQL self joins can come in handy.&lt;/p&gt;

&lt;p&gt;I hope you found this article helpful. Please let me know if you have any feedback. Happy querying!&lt;/p&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
