More on this book
Community
Kindle Notes & Highlights
by
Andy Hunt
Read between
October 2 - December 26, 2020
Why is writing concurrent and parallel code so difficult? One reason is that we learned to program using sequential systems, and our languages have features that are relatively safe when used sequentially but become a liability once two things can happen at the same time. One of the biggest culprits here is shared state. This doesn’t just mean global variables: any time two or more chunks of code hold references to the same piece of mutable data, you have shared state.
Concurrent and parallel code used to be exotic. Now it is required.
We need to allow for concurrency and to think about decoupling any time or order dependencies. In doing so, we can gain flexibility and reduce any time-based dependencies in many areas of development: workflow analysis, architecture, design, and deployment.
An activity diagram consists of a set of actions drawn as rounded boxes. The arrow leaving an action leads to either another action (which can start once the first action completes) or to a thick line called a synchronization bar. Once all the actions leading into a synchronization bar are complete, you can then proceed along any arrows leaving the bar. An action with no arrows leading into it can be started at any time.
use activity diagrams to maximize parallelism by identifying activities that could be performed in parallel, but aren’t.
Activity diagrams show the potential areas of concurrency, but have nothing to say about whether these areas are worth exploiting.
A lot of attention is given to shared memory as a source of concurrency problems, but in fact the problems can pop up anywhere where your application code shares mutable resources: files, databases, external services, and so on. Whenever two or more instances of your code can access some resource at the same time, you’re looking at a potential problem.
concurrency in a shared resource environment is difficult, and managing it yourself is fraught with challenges.
An actor is an independent virtual processor with its own local (and private) state.
A process is typically a more general-purpose virtual processor, often implemented by the operating system to facilitate concurrency.
The only state in the system is held in messages and in the local state of each actor. Messages cannot be examined except by being read by their recipient, and local state is inaccessible outside the actor.
An actor processes each message to completion, and only processes one message at a time.
Developers who don’t actively think about their code are programming by coincidence—the code might work, but there’s no particular reason why.
Testing is not about finding bugs, it’s about getting feedback on your code: aspects of design, the API, coupling, and so on. That means that the major benefits of testing happen when you think about and write the tests, not just when you run them.
As you gain experience as a programmer, your brain is laying down layers of tacit knowledge: things that work, things that don’t work, the probable causes of a type of error, all the things you notice throughout your days.
We should avoid programming by coincidence—relying on luck and accidental successes—in favor of programming deliberately.
Accidents of implementation are things that happen simply because that’s the way the code is currently written. You end up relying on undocumented error or boundary conditions.
For routines you call, rely only on documented behavior. If you can’t, for whatever reason, then document your assumption well.
Are you relying on English-speaking users? Literate users? What else are you relying on that isn’t guaranteed?
Finding an answer that happens to fit is not the same as the right answer.
Assumptions that aren’t based on well-established facts are the bane of all projects.
Can you explain the code, in detail, to a more junior programmer? If not, perhaps you are relying on coincidences.
Don’t just test your code, but test your assumptions as well. Don’t guess; actually try it. Write an assertion to test your assumptions (see Topic 25, Assertive Programming). If your assertion is right, you have improved the documentation in your code. If you discover your assumption is wrong, then count yourself lucky.
If you have an algorithm that is , try to find a divide-and-conquer approach that will take you down to .
be wary of premature optimization. It’s always a good idea to make sure an algorithm really is a bottleneck before investing your precious time trying to improve it.
Rather than construction, software is more like gardening—it is more organic than concrete. You plant many things in a garden according to an initial plan and conditions. Some thrive, others are destined to end up as compost.
Next time you see a piece of code that isn’t quite as it should be, fix it. Manage the pain: if it hurts now, but is going to hurt even more later, you might as well get it over with.
Thinking about writing a test for our method made us look at it from the outside, as if we were a client of the code, and not its author.
If you think about testing boundary conditions and how that will work before you start coding, you may well find the patterns in the logic that’ll simplify the function.
By all means practice TDD. But, if you do, don’t forget to stop every now and then and look at the big picture. It is easy to become seduced by the green "tests passed" message, writing lots of code that doesn’t actually get you closer to a solution.
Log messages should be in a regular, consistent format; you may want to parse them automatically to deduce processing time or logic paths that the program took. Poorly or inconsistently formatted diagnostics are just so much “spew”—they are difficult to read and impractical to parse.
All software you write will be tested—if not by you and your team, then by the eventual users—so you might as well plan on testing it thoroughly.
The worst choice is often called “Test Later,” but who are you kidding? “Test Later” really means “Test Never.”
They make you think about your code in terms of invariants and contracts; you think about what must not change, and what must be true. This extra insight has a magical effect on your code, removing edge cases and highlighting functions that leave data in an inconsistent state.
Good fences make good neighbors. Robert Frost, Mending Wall
Never trust data from an external entity, always sanitize it before passing it on to a database, view rendering, or other processing.[63]
don’t automatically grab the highest permission level, such as root or Administrator. If that high level is needed, take it, do the minimum amount of work, and relinquish your permission quickly to reduce the risk.
Do not restrict special characters such as []();&%$# or /. See the note about Bobby Tables earlier in this section. If special characters in your password will compromise your system, you have bigger problems.
Do not disable the paste function in the browser. Crippling the functionality of the browser and password managers does not make your system more secure, in fact it drives users to create simpler, shorter passwords that are much easier to compromise.
You want to encourage long, random passwords with a high degree of entropy. Putting artificial constraints limits entropy and encourages bad password habits, leaving your user’s accounts vulnerable to takeover.
The beginning of wisdom is to call things by their proper name. Confucius
What’s in a name? When we’re programming, the answer is “everything!”
If you aren’t vigilant about updating names as you go, you can quickly descend into a nightmare much worse than meaningless names: misleading names.
Note from the battle-scarred: UTC is there for a reason. Use it.
Perfection is achieved, not when there is nothing left to add but when there is nothing left to take away... Antoine de St. Exupery, Wind, Sand, and Stars, 1939