"The Programmer's Path to Mastery"#
Type: Life, Tools
author: David Thomas, Andrew Hunt
I had heard of the previous edition of this book, "From Novice to Expert," but at the time I thought the translated title was terrible, and no one around me recommended it, so I dismissed the idea of reading it. Then this year, I discovered the reissue of the book, now titled "The Programmer's Path to Mastery." Seizing the opportunity to read something light and take advantage of a discount on JD, I bought it. After reading two chapters, I realized I had missed out on a book two years ago that could quickly provide experience and enhance productivity. Some books are meant for casual reading, some can be devoured, and a few need to be chewed and digested. I believe this book falls into the latter category, helping you extract reasons for previous successful projects and allowing you to reflect on how to handle similar issues in life and work.
Pragmatic Approaches#
Your Life is Yours#
I am not here to fulfill your expectations, just as you are not here to fulfill mine.
— Bruce Lee
Software development is undoubtedly one of the professions you can control the most. Our skills are in high demand, our knowledge is not limited by geography, and we can work remotely. We earn a good income. We can truly do anything we want. However, there are always reasons that lead developers to refuse to change; they curl up and hope things will get better on their own, watching their skills become outdated while complaining about the ever-changing world and poor work environments.
Although it may seem that the author has a "why not eat meat porridge" attitude, in today's industry segmentation, software development is indeed one of the most self-manageable professions. Almost all the information you need is available on the internet, and even if you only know one programming language, this year you can work in finance, next year seamlessly transition to e-commerce, and the year after that move into automotive technology; your professional experience will still largely apply. The professional environment does not impose significant limitations on your career; the only limitations are those you impose on yourself.
My Source Code Was Eaten by a Cat#
If you face the risk of a vendor being unable to help, you should have a contingency plan. If the disk crashes—where all your source code is stored—that's your fault. Telling your boss, "My source code was eaten by a cat" won't solve the problem.
Team Trust#
Your team needs to trust you and rely on you—you should also feel comfortable relying on each of them.
- You don’t need to control every aspect directly.
- Express your thoughts and share your ideas. Create a healthy environment based on trust.
Taking Responsibility#
Before taking on a requirement or task, you must analyze the risks that are beyond your control. If the ethical implications of responsibility are too vague, or if faced with an unachievable situation, or if the risks are too great, you have the right not to take responsibility. At this stage, raise the issue promptly, reassess the feasibility of the task, or clarify who is responsible for the risk situation.
When deciding to take responsibility for an outcome, understand that you will assume related obligations. When you make a mistake or a poor judgment, honestly acknowledge it and try to offer alternatives.
Offer choices, not excuses. Offering choices does not mean you must always have a plan B, as in the previous example, where you can pull a time machine out of your pocket even in an apocalypse. Rather, it means that before you run over to tell them the bad news, it’s best to explain what can be done to remedy the situation. Teammates and leaders are more willing to work with someone like this; they can engage with new problems and requirements at a lower cost in such scenarios.
Software Entropy#
While software development is not constrained by most physical laws, we cannot avoid the blows from increasing entropy. Entropy is a physics term that defines the total amount of "disorder" in a system. Unfortunately, the laws of thermodynamics dictate that entropy in the universe tends to maximize. Due to various bug fixes, new and supplementary requirements, the complexity and disorder of software are gradually increasing. This section mainly discusses how to avoid the increase of software entropy.
- "Broken Window Theory"—once rules are established, they must be strictly followed, such as Code Review, Code Lint, software architecture, etc. These rules are designed to maintain the readability of software and reduce program complexity; once established, they must be adhered to, rather than being superficial. Once a window starts to break, a well-functioning system will quickly deteriorate.
- "Leading Sheep Effect"—when necessary, take the initiative to assume responsibility and act as a catalyst for change.
- "Boiling Frog"—keep an eye on the big picture, continuously review what is happening around you, and do not focus solely on what you are doing. Even if you are only responsible for part of the tasks, you need to remain sensitive to events that can impact you on a macro level.
Good Enough Software#
In the pursuit of better, we have destroyed what was already good enough.
— William Shakespeare, "King Lear"
The real world will not allow us to produce truly perfect products, especially software without bugs. Time, technology, and impatience work against us. For software developers, it is essential to understand the following points:
- View quality as a requirement issue—regarding the system you create, its application domain and the quality to be achieved must be discussed as part of the system requirements.
- Involve users in trade-offs—ignoring user needs and piling on features while repeatedly refining code is unprofessional. You may promise an unrealistic timeline, then rush to meet the deadline by cutting back on the project.
- Know when to stop—do not let excessive embellishment and refinement erode a good program. Move forward and let the code reside in its rightful place for a while. It may not be perfect, but that’s okay—it may not affect performance or functionality, and you may never need to change it again.
Knowledge Combination#
Invest in knowledge, and the returns will be the best.
— Benjamin Franklin
Knowledge and experience are indeed your most important professional assets, but unfortunately, they are time-sensitive assets. With the emergence of new technologies and the evolution of languages and environments, your knowledge can become outdated. Constantly changing market forces may render experience obsolete and irrelevant. Given the rapid pace of technological change, this can happen particularly quickly. As the value of your knowledge declines, so does your value to the company and clients. To prevent this from happening, the ability to learn new things is your crucial strategic asset.
Managing knowledge combinations is very similar to managing financial investment portfolios:
- Regular investments have a habit of periodic investment—keep your professional perspective updated, learn about new tools and skills, and read relevant news and technical articles.
- Diversification is key to long-term success—expanding your skill set means the more familiar skills you have, the better you can adapt to change.
- Smart investors will balance conservative and high-risk return investments—stay informed about new industry trends and try to engage and learn; don’t just be a listener, actively participate.
- Investors use buy low, sell high to maximize returns—such as big data and front-end development in recent years, and now low-code and connected vehicles. They may have relatively high premiums compared to usual; if you have sufficient ability and reserves, you can achieve significant improvements in work returns, but it is essential to clarify that after the blue ocean returns to the red ocean, there may also be considerable risks. You need to have the ability to cope with risks.
- Regularly review and rebalance your investment portfolio—reassess your technology tree based on current industry trends and adjust your learning priorities.
Learning Opportunities#
- Prepare some materials in advance that are easily accessible, so you can retrieve them promptly when interested.
- Treat finding answers as a personal challenge, trying various methods to update your knowledge combination.
Critical Thinking#
Critically think about what you read and hear. You need to ensure that the knowledge in your combination is accurate and not influenced by the creator.
Critical thinking is a complete discipline; here’s a start:
- Ask "Five Whys"—after getting an answer, continue asking why to get closer to the root cause. Be mindful of communication techniques to avoid being perceived as confrontational.
- Who benefits from this?—while it may sound somewhat cynical, it helps clarify the context.
- What is the background?—everything happens in its own context, which is why solutions that "solve all problems" usually do not exist, and books or articles selling "best practices" often do not hold up under scrutiny (good for a laugh).
Communication#
I think being looked at from head to toe is better than being ignored.
— "The Ninety-Year-Old Beauty"
Just having something is not enough; it also depends on how you package it. Even with the best ideas, beautiful code, and the most pragmatic thoughts, if you cannot communicate them with others, they will ultimately bear no fruit.
Treat your native language as a programming language; write articles in natural language as if writing code, respecting principles like DRY, ETC, automation, etc. (to be discussed later).
- Understand your audience—choose different ways and styles to describe the system you are introducing based on your audience.
- Know what you want to say—write a good outline and distill the core message.
- Choose the right timing.
- Involve the audience—turn the meeting into a conversation; you can express your views more effectively and listen to their feedback, drawing on their wisdom.
Pragmatic Approaches#
Some techniques and methods apply to all levels of software development, and the ideas contained have almost become axioms; they are also very general in implementation. However, these methods are rarely formalized into documentation. Now, these ideas and processes are concentrated here, forming principles like DRY, ETC, and methodologies like prototyping and note-taking.
ETC (Easier To Change)#
Good design is adaptable to users. For code, it means accommodating change (making code easier to read, decoupled, single responsibility, and replaceable). ETC (Easier To Change) is a value concept, not a rule; when you think in the software domain, ETC serves as a guide to help you choose a path among different routes.
When you have the ability to discern, common sense usually does not fail. If you cannot find a clue, you can do the following:
- Assume you are uncertain about what form of change will occur, then go back to the potential source of the problem—make what you write replaceable. This way, no matter what happens in the future, this piece of code will not become an obstacle.
- Treat it as a way to cultivate intuition. Leave a record in your engineering diary of the situations you face: what choices you have and some guesses about changes. This will help when you need to modify this code later. When you encounter similar forks in the road, this will be helpful.
DRY (Don't Repeat Yourself)#
Knowledge is not stable; it changes—often at a high frequency. Just a meeting with a client can immediately change your understanding of requirements. A government regulation changes, and some logic becomes outdated. When we maintain, we must find expressions that change things—those knowledge capsules embedded in the program. The problem is that copying knowledge in specifications, processes, and development programs is too easy; once we start doing this, we invite the nightmare of maintenance. To develop software reliably or make development projects easier to understand and maintain, the only way is to follow the DRY principle—every piece of knowledge in a system must be expressed in a single, unambiguous, authoritative way.
DRY means "Don't Repeat Yourself," so it does not only refer to "don't copy and paste code." This is indeed a component of DRY, but it is a small part and not very important.
DRY targets the duplication of knowledge and intent; it emphasizes that if two places express the same thing but in different ways, it violates the DRY principle. Similarly, if two expressions are identical but serve different responsibilities in different modules, they do not violate the DRY principle.
Code Lab#
- Unified access principle—described in "Object-Oriented Software Construction": the services provided by a module should be offered through a unified convention that does not reveal whether its implementation is based on storage or computation.
- Your efforts should be directed toward cultivating an environment where existing things are easier to find and reuse, rather than rewriting them yourself. If reuse is not easy, people will not do it. If you fail to reuse, you risk duplicating knowledge.
Orthogonality#
"Orthogonality" is a term borrowed from geometry. If two lines intersect at a right angle, they are orthogonal.
Line segments AB and CD are orthogonal to each other.
In computer science, this term symbolizes independence and decoupling. In a well-architected system, two modules should be independent of each other, meaning that a modification in one module should not affect the other. Orthogonal systems can enhance productivity and reduce risks, better coping with ever-changing business and requirements.
How to maintain system orthogonality:
- Keep code decoupled, avoid global data, and avoid similar functions.
- Cultivate the habit of constantly questioning the code. Whenever possible, reorganize and improve its structure and orthogonality.
- Systems designed and implemented based on orthogonality are easier to test. Therefore, writing unit tests is itself an interesting orthogonality test. What needs to be done to get unit tests to build and run? If you need to import most of the rest of the system's code, congratulations, you have discovered a module that is not well decoupled from the rest of the system.
- Document and evaluate the repair plans and scope of issues, archiving and recording already fixed problems, analyzing the trends in the number of modules and files affected by each bug fix in the reading reports. This helps identify unstable modules in the system or poorly designed orthogonal modules.
Reversibility#
- Maintain a flexible architecture.
- Abandon the pursuit of fashion.
Tracer Bullet#
Tracer bullets can quickly reach their target, allowing the shooter to receive immediate feedback—if the tracer bullet hits the target, subsequent regular bullets will also hit.
Similarly, this principle applies to project work. Especially when building something from what has been done before, we can engage in tracer bullet-style development.
Advantages#
- Able to construct a framework (Core) within which to work.
- Provides a better sense of progress; measuring performance and demonstrating progress to users is much easier through these cases.
- Lower trial-and-error costs; you can quickly communicate and demonstrate the core part to users to confirm if it is what they want. Collect feedback on the program at a faster pace and lower cost, leading to a more accurate version.
Methodology#
- During requirement reviews and development phases, identify the core parts that define the system and where there are significant risks. Then prioritize these parts and address them first.
- Note that the tracer bullet scenario is about quickly constructing a prototype of a complete system, not creating a prototype.
Difference from Prototyping#
Prototyping generates one-time code, while tracer bullets, although simple, are complete and form part of the final framework. Prototypes are used to validate the feasibility of a solution, while tracer bullets are used to validate the feasibility of the system.
Prototyping#
Prototyping is for learning experiences. Its value lies not in the code produced but in the lessons learned. This is the essence of prototyping.
Prototyping is much cheaper than creating a complete product. Therefore, we can analyze and expose risks through prototyping, gaining opportunities for correction in a significantly cost-reducing manner. Prototypes are designed solely to answer questions, allowing many unimportant details to be overlooked. However, if you find yourself in an environment where details cannot be ignored, then tracer bullet-style development may be more suitable for you.
When you create a prototype, which details can be overlooked:
- Correctness—you can substitute data at appropriate times.
- Completeness—the prototype only needs to meet priority functions.
- Robustness—errors and boundary conditions are not mandatory; you only need to validate specific routes.
- Format—there is no need for excessive comments and documentation.
When creating a prototype, try to postpone thinking about details; what you need to determine is how the various parts of the system combine to form a whole.
Domain Language#
The limits of my language mean the limits of my world.
— Ludwig Wittgenstein
Computer languages influence how you think about problems and how you view the dissemination of information. Each language has a list of characteristics—static typing, dynamic typing, mixins, functional or object-oriented—all of which can either provide suggestions or disrupt clarity in problem-solving. In some cases, high-level programmers can cross to the next level, not by writing code with a vocabulary list, but by programming directly in the language of the domain, using the vocabulary, syntax, and semantics of that domain (DSL).
For traditional languages like Java, we may need to write additional parsers when using external languages like XML or JSON. However, for modern languages like Kotlin, their support for DSLs is excellent. We can easily extend DSL languages using existing vocabularies.
From another perspective, consider the rise and trend of ChatGPT and Copilot. Delegating most of the work of writing business code to AI will improve our work efficiency; ideally, we only need to provide well-defined boundaries and pruning operations. However, how to convert business requirements into text that language models can clearly understand also tests our ability to describe requirements and environments clearly in our native language, enabling the language model to generate the required code based on our needs. In future requirement scenarios, we may need to cultivate our ability to write pseudocode in our native language to cope with the rapidly evolving AI landscape and enhance our work efficiency.
Estimation#
When receiving a problem or requirement, we can quickly assess the feasibility of the event through estimation. The premise of estimation is—modeling the problem. When we understand the problem or requirement, we start to build a rough mental framework model for it. During the modeling process, you can discover some potential patterns and processes that are not immediately apparent. Once you have the model, you can break it down into components, and you need to uncover the mathematical rules governing how these components interact.
How to Estimate Project Progress#
- RERT (Program Evaluation Review Technique)—each PERT task has an optimistic, a probable, and a pessimistic estimate. This range-based estimation helps avoid the most common estimation errors.
- Incremental development: divide tasks into different stages, refining experiences after each iteration to improve progress control.
Problem modeling is also a core methodology for enabling AI programming; only through modeling can we clearly express requirements or problems, break down various nodes into components, and clarify their responsibilities and functions before actual code implementation.
Basic Tools#
Plain Text#
As programmers, our basic material is not wood or iron blocks, but knowledge. We collect requirements in the form of knowledge and express this knowledge in design, implementation, testing, and documentation. Plain text is what we consider the best format for storing knowledge permanently.
Plain text does not just refer to txt format files; it encompasses any text data that can be directly read by humans and parsed by editors. The advantages of plain text are:
- Protection against obsolescence—many knowledge software applications today rely on the internet and maintenance for use and updates. When the application lifecycle of the data storage ends, if it does not support exporting plain text data, it becomes extremely difficult to reuse the knowledge stored in the application.
- Easy retrieval—using plain text records, we can seamlessly use version control to manage modification records, search and match your knowledge through Shell functions, and quickly find information in text editors without relying on specific software and their poor search functionalities (like WeChat).
- Proficient use of editors and Shell—the advantage of graphical tools lies in WYSIWYG (what you see is what you get); the downside is WYSIAYG (what you see is all you get). If the designer of the graphical tool has not designed hooks for your additional needs, you simply cannot achieve them. With the powerful ecosystem and versatility of editors and Shell, you can combine them to gain significant capabilities, and this knowledge is universal; it is not limited to a specific IDE.
Engineering Diary#
Benefits of keeping a diary:
- It is more reliable than memory.
- It provides a place to save thoughts unrelated to the current task, allowing you to continue focusing on what you are doing while ensuring that great ideas are not forgotten.
- It acts like a rubber duck. When you stop and write things down, your brain may shift gears, almost as if you are talking to someone—this is a great opportunity for reflection. You may suddenly realize that what you just did, which is the subject of your notes, is completely wrong.
Another added benefit is that you can occasionally recall what you were doing years ago, remembering those people, those events, and those terrible clothes and hairstyles.
Pragmatic Paranoia#
There is often a luxury in self-blame. When we blame ourselves, we always feel that others have no right to blame us.
— Oscar Wilde, "The Picture of Dorian Gray"
BDC Contract Programming#
TDD Test-Driven Development#
The author discusses the development models of BDC and TDD, mainly advocating for defensive programming and assertion-based programming—thinking through boundary conditions thoroughly during the code design phase and performing checks. When problems arise, exceptions should be thrown early to avoid further harm to the entire system. This is a fundamental skill for a competent programmer, and there is nothing particularly methodological about it. What is commendable is the switchability of assertion programming; we can enable assertions only in debug mode, similar to Google source code, which can expose some issues during daily development and testing processes. In production mode, adhering to user priority and performance loss principles, we can turn off assertions and supplement with logs.
Many times, tomorrow may look much like today, but do not expect it to be so.#
- When designing and maintaining, it is not always necessary to waste energy designing for an uncertain future beyond the visible range of modules (which adds complexity, like another book mentions, when Android version 0.x was released, hardware did not support GPU, and the Google engineers responsible for graphics rendering did not design the entire system but involved a virtual GPU. When later versions of Android supported GPU, they only needed to replace the virtual GPU module, and the system design remained intact). It is better to design code to be replaceable. Making code replaceable also helps improve internal cohesion, decoupling, and DRY, leading to better design.
- In software development, always take small, well-considered steps, checking feedback continuously and adjusting before proceeding. Treat the rate of feedback as a speed limit; never undertake tasks that are "too big."