More on this book
Community
Kindle Notes & Highlights
by
Andy Hunt
Read between
October 2 - December 26, 2020
Be sure not to confuse requirements that are fixed, inviolate laws with those that are merely policies that might change with a new management regime. That’s why we use the term semantic invariants—it must be central to the very meaning of a thing, and not subject to the whims of policy (which is what more dynamic business rules are for).
with enough components and agents that can negotiate their own contracts among themselves to achieve a goal, we might just solve the software productivity crisis by letting software solve it for us.
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!
All errors give you information. You could convince yourself that the error can’t happen, and choose to ignore it. Instead, Pragmatic Programmers tell themselves that if there is an error, something very, very bad has happened. Don’t forget to Read the Damn Error Message
One of the benefits of detecting problems as soon as you can is that you can crash earlier, and crashing is often the best thing you can do. The alternative may be to continue, writing corrupted data to some vital database or commanding the washing machine into its twentieth consecutive spin cycle.
“Defensive programming is a waste of time. Let it crash!”
when your code discovers that something that was supposed to be impossible just happened, your program is no longer viable. Anything it does from this point forward becomes suspect, so terminate it as soon as possible.
A dead program normally does a lot less damage than a crippled one.
There is a luxury in self-reproach. When we blame ourselves we feel no one else has a right to blame us. Oscar Wilde,
If you need to free resources, catch the assertion’s exception or trap the exit, and run your own error handler. Just make sure the code you execute in those dying milliseconds doesn’t rely on the information that triggered the assertion failure in the first place.
It’s embarrassing when the code we add to detect errors actually ends up creating new errors. This can happen with assertions if evaluating the condition has side effects.
In reality, for any complex program you are unlikely to test even a minuscule percentage of the permutations your code will be put through.
Keeping this in mind at all times will make you humble enough to change your mindset from an arrogant state to a helpful one whenever a bug is reported (in spite of having a test suite in place)
Turning off assertions when you deliver a program to production is like crossing a high wire without a net because you once made it across in practice.
If you’re adding logging records in a database, is there a similar process in place to expire them? For anything that you create that takes up a finite resource, consider how to balance it.
The basic pattern for resource allocation can be extended for routines that need more than one resource at a time. There are just two more suggestions: 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. When allocating the same set of resources in different places in your code, always allocate them in the same order. This will reduce the possibility of deadlock.
If an exception is thrown, how do you guarantee that everything allocated prior to the exception is tidied up? The answer depends to some extent on the language support. You generally have two choices: Use variable scope (for example, stack variables in C++ or Rust) Use a finally clause in a try…catch block
Because Pragmatic Programmers trust no one, including ourselves, we feel that it is always a good idea to build code that actually checks that resources are indeed freed appropriately.
What’s a task that’s too big? Any task that requires “fortune telling.”
we hear you cry, aren’t we supposed to design for future maintenance? Yes, but only to a point: only as far ahead as you can see. The more you have to predict what the future will look like, the more risk you incur that you’ll be wrong. Instead of wasting effort designing for an uncertain future, you can always fall back on designing your code to be replaceable.
we’ll tell you how to make reversible decisions, so your code can stay flexible and adaptable in the face of an uncertain world.
Coupling is the enemy of change, because it links together things that must change in parallel.
to make matters worse, coupling is transitive: if A is coupled to B and C, and B is coupled to M and N, and C to X and Y, then A is actually coupled to B, C, M, N, X, and Y.
Developers who are afraid to change code because they aren’t sure what might be affected.
Meetings where everyone has to attend because no one is sure who will be affected by a change.
Try not to have more than one “.” when you access something. And access something also covers cases where you use intermediate variables,
There’s a big exception to the one-dot rule: the rule doesn’t apply if the things you’re chaining are really, really unlikely to change. In practice, anything in your application should be considered likely to change. Anything in a third-party library should be considered volatile, particularly if the maintainers of that library are known to change APIs between releases. Libraries that come with the language, however, are probably pretty stable,
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.
Each piece of global data acts as if every method in your application suddenly gained an additional parameter: after all, that global data is available inside every method.
Our experience has been that reuse should probably not be a primary concern when creating code, but the thinking that goes into making code reusable should be part of your coding routine. When you make code reusable, you give it clean interfaces, decoupling it from the rest of your code. This allows you to extract a method or module without dragging everything else along with it.
If all you have is a singleton with a bunch of exported instance variables, then it’s still just global data. It just has a longer name.
Any mutable external resource is global data. If your application uses a database, datastore, file system, service API, and so on, it risks falling into the globalization trap. Again, the solution is to make sure you always wrap these resources behind code that you control.
Coupled code is hard to change: alterations in one place can have secondary effects elsewhere in the code, and often in hard-to-find places that only come to light a month later in production.
Things don’t just happen; they are made to happen. John F. Kennedy
computers have to integrate into our world, not the other way around. And our world is messy: things are constantly happening, stuff gets moved around, we change our minds, …. And the applications we write somehow have to work out what to do.
An event represents the availability of information. It might come from the outside world: a user clicking a button, or a stock quote update. It might be internal: the result of a calculation is ready, a search finishes.
if we write applications that respond to events, and adjust what they do based on those events, those applications will work better in the real world. Their users will find them to be more interactive, and the applications themselves will make better use of resources.
A state machine is basically just a specification of how to handle events. It consists of a set of states, one of which is the current state. For each state, we list the events that are significant to that state. For each of those events, we define the new current state of the system.
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.
Streams let us treat events as if they were a collection of data.
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.
we need to get back to thinking of programs as being something that transforms inputs into outputs. When we do, many of the details we previously worried about just evaporate. The structure becomes clearer, the error handling more consistent, and the coupling drops way down.
You might think that this is just syntactic sugar. But in a very real way the pipeline operator is a revolutionary opportunity to think differently. Using a pipeline means that you’re automatically thinking in terms of transforming data; each time you see |> you’re actually seeing a place where data is flowing between one transformation and the next.
If your background is object-oriented programming, then your reflexes demand that you hide data, encapsulating it inside objects.
Data becomes a peer to functionality: a pipeline is a sequence of code → data → code → data…. The data is no longer tied to a particular group of functions, as it is in a class definition. Instead it is free to represent the unfolding progress of our application as it transforms its inputs into its outputs. This means that we can greatly reduce coupling: a function can be used (and reused) anywhere its parameters match the output of some other function.
Do you program in an object-oriented language? Do you use inheritance? If so, stop! It probably isn’t what you want to do.
If a parent class has 20 methods, and the subclass wants to make use of just two of them, its objects will still have the other 18 just lying around and callable.
When code relies on values that may change after the application has gone live, keep those values external to the app.
look for anything that you know will have to change that you can express outside your main body of code, and slap it into some configuration bucket.
Concurrency is when the execution of two or more pieces of code act as if they run at the same time. Parallelism is when they do run at the same time. To have concurrency, you need to run code in an environment that can switch execution between different parts of your code when it is running. This is often implemented using things such as fibers, threads, and processes. To have parallelism, you need hardware that can do two things at once. This might be multiple cores in a CPU, multiple CPUs in a computer, or multiple computers connected together.