More on this book
Community
Kindle Notes & Highlights
Here are some specific areas you may want to look for in the architectural prototype: Are the responsibilities of the major areas well defined and appropriate? Are the collaborations between major components well defined? Is coupling minimized? Can you identify potential sources of duplication? Are interface definitions and constraints acceptable? Does every module have an access path to the data it needs during execution? Does it have that access when it needs it?
Before you embark on any code-based prototyping, make sure that everyone understands that you are writing disposable code. Prototypes can be deceptively attractive to people who don’t know that they are just prototypes. You must make it very clear that this code is disposable, incomplete, and unable to be completed.
If you feel there is a strong possibility in your environment or culture that the purpose of prototype code may be misinterpreted, you may be better off with the tracer bullet approach. You’ll end up with a solid framework on which to base future development.
We always try to write code using the vocabulary of the application domain (see Maintain a Glossary). In some cases, Pragmatic Programmers can go to the next level and actually program using the vocabulary, syntax, and semantics—the language—of the domain.
They employ some fairly devious code, including metaprogramming and macros, but ultimately they are compiled and run as regular code.
The downside of internal domain languages is that you’re bound by the syntax and semantics of that language. Although some languages are remarkably flexible in this regards, you’re still forced to compromise between the language you want and the language you can implement.
Finally, there’s a cheat for creating internal domain languages if you don’t mind the host language syntax leaking through. Don’t do a bunch of metaprogramming. Instead, just write functions to do the work.
If you come across a situation where you feel your current tools can’t cut it, make a note to look for something different or more powerful that would have helped.
Human-readable forms of data, and self-describing data, will outlive all other forms of data and the applications that created them. Period. As long as the data survives, you will have a chance to be able to use it—potentially long after the original application that wrote it is defunct. You can parse such a file with only partial knowledge of its format; with most binary files, you must know all the details of the entire format in order to parse it successfully.
I'm more convinced about self describing atomic pieces of data. We need stronger primitives than plaintext can afford but we cant divorce interpretation and content. A composble protocol of some sort is necessary.
You won’t be able to automate common tasks, or use the full power of the tools available to you. And you won’t be able to combine your tools to create customized macro tools. A benefit of GUIs is WYSIWYG—what you see is what you get. The disadvantage is WYSIAYG—what you see is all you get.
Failure of our primitives and toolkits. Theres also a point about wysiwyg and its relation to algebraic reasoning. The visual representation and discoverability need to be divorced from automation or composability.
over the course of a year, you might actually gain an additional week if you make your editing just 4% more efficient and you edit for 20 hours a week.
When editing code, move by various syntactic units (matching delimiters, functions, modules, …).
It doesn’t really matter whether the bug is your fault or someone else’s. It is still your problem.
If your first reaction on witnessing a bug or seeing a bug report is “that’s impossible,” you are plainly wrong.
We routinely set compiler warning levels as high as possible. It doesn’t make sense to waste time trying to find a problem that the computer could find for you! We need to concentrate on the harder problems at hand.
Artificial tests (such as the programmer’s single brush stroke from bottom to top) don’t exercise enough of an application. You must brutally test both boundary conditions and realistic end-user usage patterns. You need to do this systematically (see Ruthless and Continuous Testing
Read the Damn Error Message
The OS is probably not broken.
But Pragmatic Programmers take this a step further. They don’t trust themselves, either. Knowing that no one writes perfect code, including themselves, Pragmatic Programmers build in defenses against their own mistakes.
Points to ponder: If DBC is so powerful, why isn’t it used more widely? Is it hard to come up with the contract? Does it make you think about issues you’d rather ignore for now? Does it force you to THINK!? Clearly, this is a dangerous tool!
Deallocate resources in the opposite order to that in which you allocate them. That way you won’t orphan resources if one resource contains references to another.
Use variable scope (for example, stack variables in C++ or Rust)
Take Small Steps—Always
Always take small, deliberate steps, checking for feedback and adjusting before proceeding. Consider that the rate of feedback is your speed limit. You never take on a step or a task that’s “too big.”
What’s a task that’s too big? Any task that requires “fortune telling.” Just as the car headlights have limited throw, we can only see into the future perhaps one or two steps, maybe a few hours or days at most. Beyond that, you can quickly get past educated guess and into wild speculation. You might find yourself slipping into fortune telling when you have to: Estimate completion dates months in the future Plan a design for future maintenance or extendability Guess user’s future needs Guess future tech availability
This principle says that you shouldn’t make decisions based on the internal state of an object and then update that object. Doing so totally destroys the benefits of encapsulation and, in doing so, spreads the knowledge of the implementation throughout the code. So the first fix for our train wreck is to delegate the discounting to the total object:
In Topic 30, Transforming Programming we talk about composing functions into pipelines. These pipelines transform data, passing it from one function to the next. This is not the same as a train wreck of method calls, as we are not relying on hidden implementation details.
The distinction bere is lost on me unless it means immutability or certinn functions are more obvious
If It’s Important Enough to Be Global, Wrap It in an API
We discuss Tell, Don’t Ask in our 2003 Software Construction article The Art of Enbugging.
Things don’t just happen; they are made to happen. John F. Kennedy
State machines are underused by developers, and we’d like to encourage you to look for opportunities to apply them.
Pubsub is a good technology for decoupling the handling of asynchronous events. It allows code to be added and replaced, potentially while the application is running, without altering existing code. The downside is that it can be hard to see what is going on in a system that uses pubsub heavily: you can’t look at a publisher and immediately see which subscribers are involved with a particular message.
The current de facto baseline for reactive event handling is defined on the site http://reactivex.io, which defines a language-agnostic set of principles and documents some common implementations. Here we’ll use the RxJs library for JavaScript.
Events are everywhere. Some are obvious: a button click, a timer expiring. Other are less so: someone logging in, a line in a file matching a pattern. But whatever their source, code that’s crafted around events can be more responsive and better decoupled than its more linear counterpart.
Sometimes the easiest way to find the transformations is to start with the requirement and determine its inputs and outputs. Now you’ve defined the function representing the overall program. You can then find steps that lead you from input to output. This is a top-down approach.
const content = File.read(file_name); const lines = find_matching_lines(content, pattern) const result = truncate_lines(lines) Many people write OO code by chaining together method calls, and might be tempted to write this as something like: const result = content_of(file_name) .find_matching_lines(pattern) .truncate_lines() What’s the difference between these two pieces of code? Which do you think we prefer?
Do you program in an object-oriented language? Do you use inheritance? If so, stop!
“differential programming” (meaning: various ways to accomplish “this is like that except”).