- Published on
Becoming a Pragmatic Programmer: 101 Quick Key Takeaways from "The Pragmatic Programmer" 20th Anniversary Edition
- Authors
- Name
- Mike Tsamis
Introduction
As a Senior Software Engineer, I am always looking for opportunities to improve my developer skills and stay up to date in an ever-changing industry. I recently read "The Pragmatic Programmer: Your Journey to Mastery, 20th Anniversary Edition" by David Thomas and Andrew Hunt and was blown away by the wealth of valuable insight and advice contained within it. I took a lot of notes and wanted to share some of the key points that stood out to me the most. In this article, I share 101 of my key takeaways from each chapter/topic within "The Pragmatic Programmer." Whether you're a seasoned developer or a novice, I hope you'll find a few gems in this summary. Let's get started!
Chapter 1: A Pragmatic Philosophy
Topic 1: It's Your Life
- It's important to take ownership of your life and career as a software developer. You have the agency to make changes and decisions that will benefit you and your professional development.
- Don't be passive in your career. Take the initiative to learn new skills, try new things, and make changes if your current work environment or job isn't meeting your needs. Don't wait for someone else to offer you opportunities, seek them out yourself.
Topic 2: The Cat Ate My Source Code
- Pragmatic programmers take responsibility for their own careers, learning, and work.
- Trust is important for the success of a team, and it is important to be honest and admit to mistakes or shortcomings.
- When you accept responsibility for an outcome, you should expect to be held accountable for it. Admit mistakes and offer options instead of making excuses.
- It is important to be proactive in identifying and addressing problems and risks, and to communicate potential issues to others in a timely manner.
Topic 3: Software Entropy
- The psychology or culture of a project can greatly contribute to software rot. A sense of hopelessness or abandonment can spread among team members, leading to a downward spiral.
- To prevent software rot, it is important to fix "broken windows," or small issues, as soon as they are discovered. Neglecting these issues can lead to serious structural damage and a sense of abandonment.
Topic 4: Stone Soup and Boiled Frogs
- Use small, incremental changes to achieve a larger goal, by showing the potential benefits and allowing others to join in the success.
- Be aware of gradual changes and their potential consequences, and actively work to prevent negative changes from occurring.
Topic 5: Good-Enough Software
- Good-enough software is software that meets the needs of the user, is maintainable, and meets basic performance, privacy, and security standards, but may not necessarily be perfect.
- It is important to know when to stop working on a program and let it stand on its own, rather than continuing to add layers and details that may ultimately harm the overall quality of the software.
- Maintaining software and fixing defects should be a continuous process, rather than a one-time effort at the end of a project.
- It is important to prioritize and fix defects based on their potential impact and difficulty of resolution.
Topic 6: Your Knowledge Portfolio
- The value of a programmer's knowledge and experience decreases over time, so it is important to continually invest in and update their knowledge portfolio to stay current and valuable to their company or clients.
- To effectively manage their knowledge portfolio, programmers should invest regularly, diversify their knowledge, balance their learning between high-risk and low-risk areas, seek out new technologies before they become popular, and regularly review and rebalance their portfolio.
- Setting specific, measurable, achievable, relevant, and time-bound goals (SMART goals) can help programmers effectively invest in their knowledge portfolio.
- Building a network of knowledgeable and experienced individuals, such as through attending industry conferences, joining professional organizations, or participating in online communities, can also help programmers stay current and expand their knowledge.
- Continually seeking out new learning opportunities, such as through self-study, online courses, or obtaining additional certifications, can also help programmers stay competitive and valuable in their field.
Topic 7: Communicate!
- Effective communication is essential for a programmer to convey ideas and work effectively with others.
- Know your audience and what you want to say before communicating. Consider using visual aids.
- Gather feedback and continuously improve your communication skills.
Chapter 2: A Pragmatic Approach
Topic 8: The Essence of Good Design
- Good design is easier to change than bad design.
- Follow the ETC principle: Easier to Change. To make code easy to change, it is important to keep it decoupled and cohesive, and to make it replaceable if possible.
Topic 9: DRY - The Evils of Duplication
- The DRY (Don't Repeat Yourself) principle states that every piece of knowledge should have a single, unambiguous, authoritative representation within a system.
- DRY applies to more than just code, it also refers to the duplication of knowledge or intent expressed in different places or formats.
- Code duplication can lead to maintenance nightmares and decreased reliability in software. To avoid it, try to identify common patterns and abstract them into reusable components. Use techniques such as inheritance, composition, and delegation to eliminate duplication.
- Use libraries, frameworks, and tools to avoid reinventing the wheel and repeating work.
Topic 10: Orthogonality
- Orthogonality is achieved when different components of a system are independent and do not rely on or affect one another.
- Orthogonal systems are simpler to work with because changes to one component do not require changes to be made to other components. It is important to design the system in a modular way and to ensure that each component has a single, well-defined purpose.
Topic 11: Reversibility
- Reversibility refers to the ability to easily change or reverse decisions, especially those that are critical or irreversible. Maintaining flexibility in architecture, design, and implementation can help a project to adapt to changes and avoid being locked into a single solution.
- Be prepared for uncertainty and don't assume that decisions are set in stone. It can help a project to be more reversible and adaptable.
Topic 12: Tracer Bullets
- Tracer bullets involve developing a simple feature that exercises multiple architectural layers to ensure they work together. They are used to identify important requirements, areas of uncertainty, and risks, and prioritize development accordingly.
- Tracer bullets allow for a gradual build-up of a system and the identification and solving of problems as they arise, rather than trying to specify and plan everything upfront. They provide immediate feedback and help to refine aim under actual conditions.
Topic 13: Prototypes and Post-it Notes
- Prototyping is a way to try out ideas and reduce risk in software development.
- Prototypes can be code or non-code based, such as using Post-it notes or drawings. Things to prototype include architecture, new functionality, data structures, third-party tools, performance issues, and UI design.
- When gathering feedback on a prototype, focus on the aspects being tested and avoid getting bogged down in details.
Topic 14: Domain Languages
- Using a programming language that reflects the vocabulary and syntax of the problem domain can make it easier to understand and solve the problem. Examples of domain languages include RSpec, Cucumber, Phoenix Routes, and Ansible.
Topic 15: Estimating
- Estimating is a skill that can help you understand the feasibility of tasks and projects, and predict outcomes.
- The accuracy of an estimate depends on the context in which it is used. Use units that reflect the intended precision of the estimate. Also, the most accurate estimates come from people who have performed similar tasks before.
- Use a top-down approach for estimating large tasks by breaking them down into smaller parts and adding estimates for each part. Use a bottom-up approach for estimating small tasks by adding up estimates for each individual task.
- Always provide a range for estimates and explain any assumptions made.
Chapter 3: The Basic Tools
Topic 16: The Power of Plain Text
- Plain text is a simple and effective way to store knowledge that can be manipulated and read by humans and computers.
- Virtually every tool in the computing universe can operate on plain text, making it a powerful and versatile format.
Topic 17: Shell Games
- The command shell is a powerful tool for manipulating text and automating tasks. Customizing the command shell can improve productivity and make it easier to use.
- GUI interfaces have limitations and can't always perform tasks outside of their intended scope.
- It is important to learn basic shell skills and become familiar with the tools available in the shell.
Topic 18: Power Editing
- To become fluent in an editor, discover new, useful features, practice using them frequently, and consider adding extensions or writing your own to automate repetitive tasks.
Topic 19: Version Control
- Always use version control, even for small projects or non-source code files like documentation.
Topic 20: Debugging
- Debugging is a problem-solving process and requires a calm, analytical mindset. It is important to avoid panic and finger-pointing.
- The first step in debugging is to reproduce the bug consistently.
- It is important to fix the root cause of a problem rather than just the symptoms.
Topic 21: Text Manipulation
- Text manipulation languages are powerful tools for quickly performing transformations on text, prototyping ideas, and automating tasks.
Topic 22: Engineering Daybooks
- An engineering daybook is a journal in which an engineer records their work, ideas, readings, and other relevant information. Keeping a daybook is more reliable than relying on memory and can provide a place to store ideas and reflect on tasks. Consider using paper for an engineering daybook rather than a digital file or a wiki.
Chapter 4: Pragmatic Paranoia
Topic 23: Design by Contract
- Design by Contract (DBC) is a technique for ensuring program correctness by documenting the rights and responsibilities of software modules. It consists of preconditions (requirements that must be met in order for the function or method to be called), postconditions (guaranteed output of the function), and class invariants (conditions that must always be true from the perspective of a caller). Adhering to DBC can help developers ensure program correctness, make code easier to read and understand, and allow for better collaboration between developers.
Topic 24: Dead Programs Tell No Lies
- Crashing early and managing failure through supervisors is an effective strategy for maintaining the reliability and integrity of systems in high-availability, fault-tolerant environments.
- In other situations, it may be necessary to handle the exception and keep the program running, but it's important to be aware of the possible consequences and make a conscious decision about how to proceed.
Topic 25: Assertive Programming
- Use assertions to check for things that should never happen in your code. Leave assertions turned on in production to protect against unexpected situations.
Topic 26: How to Balance Resources
- It is good practice for the function or object that allocates a resource to be responsible for deallocating it, in order to balance resources and prevent resource leaks.
Topic 27: Don't Outrun Your Headlights
- Take small, conscious steps in your development work and get constant feedback to adjust your approach. Before you start coding, make sure you have a solid understanding of the problem you're trying to solve.
- Avoid tasks that require "fortune telling," or predicting future events or needs, as they carry a high risk of being incorrect. Design your code to be replaceable rather than trying to anticipate and design for an uncertain future.
Chapter 5: Bend, Or Break
Topic 28: Decoupling
- Coupling is the enemy of change. Because it ties together things that need change in parallel, making change more difficult. Coupling symptoms include abnormal dependencies between unrelated modules, "simple" changes that affect unrelated parts of the system, and developers fearing code changes.
- Ways to decouple code include using smaller methods, using interfaces and abstract classes, using dependency injection, and using messages and event-driven architectures
Topic 29: Juggling the Real World
- In order to make applications more interactive and efficient, they should be designed to respond to events and adjust their behavior accordingly. There are 4 strategies for writing responsive applications: Finite State Machines, the Observer Pattern, Publish/Subscribe, and Reactive Programming and Streams.
- Finite State Machines (FSMs) are a way to specify how to handle events by defining a set of states and the events that are significant to each state, as well as the resulting new state for each event. FSMs can be represented as data in a table and implemented with simple code.
- The Observer Pattern involves objects subscribing to events and being notified when they occur.
- In the Publish/Subscribe Pattern, objects publish events and other objects subscribe to them.
- Reactive Programming and Streams involve creating and manipulating streams of events and reacting to them as they occur.
Topic 30: Transforming Programming
- It is important to focus on the transformation of data in programming, rather than the code itself. There are 2 approaches to finding the necessary transformations in a program are. First approach is starting with the requirement and determining the inputs and outputs. Second approach is starting with the data and determining the necessary transformations to reach the desired output.
Topic 31: Inheritance Tax
- Instead of using inheritance, it is often better to use composition and "duck typing" to achieve the same goals. "Duck typing" refers to a style of type checking in which an object's methods and properties determine its type, rather than its inheritance from a particular class or implementation of a specific interface. This means that if an object has the methods and properties that are needed for a particular task, it can be used in that task regardless of its actual type.
Topic 32: Configuration
- Configuration data should be kept external to the application and parameterized, allowing the code to adapt to different environments and customers. Configuration data can be stored in flat files, database tables, or behind an API.
Chapter 6: Concurrency
Topic 33: Breaking Temporal Coupling
- Concurrency can be introduced by breaking up time-consuming processes into smaller pieces that can be performed in parallel. Be careful to avoid race conditions.
- Asynchronous design can help to decouple systems and improve responsiveness. However, asynchronous code can also be complex and error-prone, so it should be thoroughly tested.
Topic 34: Shared State Is Incorrect State
- It is generally best to avoid shared state whenever possible. It can lead to incorrect or inconsistent results.
Topic 35: Actors and Processes
- Actors and processes can be used to implement concurrent systems without shared state. Actors process messages asynchronously and have their own private state, while processes can be constrained to behave like actors. Good design is important to avoid issues in actor-based systems.
Topic 36: Blackboards
- Blackboards are a form of concurrency where independent processes or agents contribute and retrieve information from a shared data structure (the blackboard). Blackboards can be used in situations where data can arrive in any order and where data gathering may be done by different systems. Blackboards can be used to coordinate and manage complex processes where data dependencies and constraints exist.
Chapter 7: While You Are Coding
Topic 37: Listen to Your Lizard Brain
- It is common for people to feel hesitation or discomfort when starting a new project or task. This hesitation may be an instinctive response based on past experience or doubts about one's ability. It is important to listen to and acknowledge these feelings in order to identify and address any potential problems.
- Coding can also be difficult and frustrating at times, and this can be a sign that there is something wrong with the structure or approach being taken. It is important to listen to and trust these instincts in order to identify and fix problems.
- In order to listen to and trust one's instincts, it is important to allow time and space for them to surface and be acknowledged, to pay attention to physical sensations and emotions, and to practice mindfulness and self-awareness.
Topic 38: Programming by Coincidence
- "Programming by coincidence" refers to relying on luck and accidental successes instead of programming deliberately. Avoid it by understanding the reason why the code works and not just relying on the fact that it seems to work. Good testing and documentation will also help remedy this.
Topic 39: Algorithm Speed
- The complexity of an algorithm should be considered when choosing which algorithm to use, as the performance of an algorithm can significantly impact the overall performance of a program. There are various techniques that can be used to optimize the performance of an algorithm, including choosing the right data structure, avoiding unnecessary work, and parallelizing work.
Topic 40: Refactoring
- Refactoring should be a day-to-day activity, taking low-risk small steps, rather than a special, high-ceremony, once-in-a-while activity. Some reasons to refactor include: duplication, nonorthogonal design, outdated knowledge, usage changes, or improving internal quality.
- Test-Driven Development (TDD) can be used to refactor code safely. Good automated unit tests are necessary to ensure that external behavior has not changed during refactoring.
Topic 41: Test to Code
- The major benefits of testing occur during the thinking and writing process, rather than just when the tests are run. Testing provides valuable feedback that can guide coding, and thinking about tests can help clarify understanding of the code and simplify its logic.
- TDD is mostly beneficial, but it can also lead to excessive focus on test coverage and redundant tests. When using TDD, it is important to remember that the goal is to produce working code, not just passing tests.
Topic 42: Property-Based Testing
- Property-based testing is a technique for automating testing using predefined properties of the code. It is used to validate assumptions made in the code and ensure that it meets its intended contracts and invariants. Property-based testing can be more effective than manually-written unit tests because it can uncover unexpected edge cases and help identify incorrect assumptions.
Topic 43: Stay Safe Out There
- Basic principles for ensuring secure software include taking measures to protect against untrusted input data, following the principle of least privilege, using secure defaults, encrypting sensitive data, and maintaining security updates. It is also important to have processes in place to identify and fix security vulnerabilities in a timely manner.
Topic 44: Naming Things
- Naming things in code is important as it reveals the intent and belief of the developer. Use descriptive names that clearly convey the purpose of the thing.
Chapter 8: Before the Project
Topic 45: The Requirements Pit
- When gathering requirements, be prepared to ask questions and explore edge cases to better understand and define the client's needs.
- Be diplomatic when addressing potential issues or challenges with the client's initial requirements or suggestions. Keep an open mind and be willing to consider different approaches to solving the client's problem.
- Be prepared for the possibility that requirements may change or evolve during the project development process and have a plan in place for managing these changes.
Topic 46: Solving Impossible Puzzles
- When faced with a difficult problem or puzzle, it is important to identify and understand the constraints and degrees of freedom involved. Don't dismiss potential solutions too quickly; consider all options and evaluate their feasibility.
- It can be helpful to take a break and work on something else if you feel stuck on a problem. Additionally, it's important to not be afraid to ask for help or seek out new resources or approaches when faced with a challenging problem.
Topic 47: Working Together
- Collaborative approaches like pair and mob programming can help improve communication within a team and lead to more cohesive and well-designed systems. For example, pair programming, where one person codes while another comments and helps solve problems, can be a powerful way of working together and producing higher quality software. Mob programming, where the entire team works together on the same problem at the same time, can also be an effective approach.
Topic 48: The Essence of Agility
- To work in an agile way, you should gather and act on feedback, and continuously make small, meaningful steps towards your goals while evaluating and fixing any mistakes you encounter.
Chapter 9: Pragmatic Projects
Topic 49: Pragmatic Teams
- Pragmatic team as a whole should prioritize maintaining high quality and actively monitor for changes in the project environment. Effective communication within the team and with stakeholders is crucial for success. Teams should strive for transparency and collaboration, and allow for flexibility and adaptability.
- Investing in team knowledge and skills through scheduled learning and development time is important for improvement and innovation.
Topic 50: Coconuts Don’t Cut It
- Consider the context and specific needs of your team or organization when choosing development methods or frameworks. Try new ideas on a small scale before fully implementing them to determine if they are effective.
Topic 51: Pragmatic Starter Kit
- Implement full automation of builds, tests, and deployments to ensure repeatability and consistency. Make sure to maintain and regularly update the automation tools and processes used on the project.
Topic 52: Delight Your Users
- Focus on delivering business value to delight users rather than just delivering working software. Understand the underlying expectations and objectives of the users and the project. Approach problem solving as a key aspect of your role as an engineer.
Topic 53: Pride and Prejudice
- Take pride in your work and be responsible for the design and code you produce. Strive to be recognized as a professional and high quality developer.
Conclusion
I hope you've found these 101 takeaways to be useful and inspiring. I've tried to capture the essence of the book's key insights and recommendations, but nothing beats reading the entire book yourself. There are a lot of code examples which I did not include in this article. If you're serious about improving your programming skills and maximizing your effectiveness as a developer, I highly recommend picking up a copy of "The Pragmatic Programmer: Your Journey to Mastery, 20th Anniversary Edition" and studying it closely.