Early adopter/fast adapter: have an instinct of technologies and techniques, love trying new things out. You can easily integrate new tech with the rest of your knowledge
Inquisitive: you ask lots of questions, whose answers might help you out in the future
Critical thinker: rarely take things as given without first getting the facts
Realistic: you understand the underlying nature of each problem and that realism gives you the stamina to get the work done
Jack of all trades: you try hard to be familiar with a broad variety of tech and keep abreast of new developments. You can always move into new spaces or challenges
Large projects, despite the engineering, is desparately in need of individual craftsmanship
Analogy: Middle Ages cathedrals had broad engineering requirements, but the individual stonecutters, masons and glass workers had to apply craftsmanship to make the cathedral look awesome
If you continuously improve on a daily basis, then you will be able to learn a lot and become a master programmer
Tips: 1, 2
A Pragmatic Philosophy
Topic 1: It’s Your Life
Development skills are in demand. We have the option to change things if we are unhappy
If you have particular desires for your job (eg. learn new skills, work remotely), then ASK! You have agency
Tips: 3
Topic 2: The Cat Ate My Source Code
You have to take responsibility for your projects, your code and your career
You may have done everything as good as you could, but things still go wrong. Regardless you need to take responsibility
Taking responsibility for your circle of control gives others a high sense of trust in you, which is essential for team morale and progress
You have the right to not take responsibility for things if you feel that parts of the project are too risky, unethical, etc. However, you need to actually do the research to figure out if you want to be
However, once you take responsibility, you need to take accountability.
Mitigate any risks that you feel could come up or create contingency plans. If they do pop up, provide solutions, not blame. Don’t provide the equivalent of ‘my cat ate my source code’
Tips: 4
Topic 3: Software Entropy
If there is one law that affects software, it is the tendency towards maximum entropy. This is known as software rot or technical debt (funny term because most teams don’t pay it back lol)
One way to easily mitigate entropy is to not start it in the first place. Don’t leave bad designs, wrong decisions or poor code unrepaired
Neglect is the most powerful reason for broken projects
Don’t be the person who starts the decline. Like firefighters who take try their best to not cause any collateral damage for their actions, programmers need to do the same
Tips: 5
Topic 4: Stone Soup and Boiled Frogs
Stone soup story:
Once a group of soldiers were returning from the war and arrived at a village. However, the village people were not willing to give them food because they had shortages
The soldiers started boiling water with stones. Curious, the villagers asked. Everytime, the soldiers answered and replied that certain things (vegetables, herbs, meat) would make it better). The villagers were too curious and hungry to understand the trick and added to the soup
At the end, the soldiers remove the stones and shared the soup with the village
Programmers sometimes have to do the same. We have a design or solution in mind, but we are met with unwilligness to share resources from other teams
We need to be catalysts and start the process with a reasonable set of requirements. We present and tell others that it would be better if xyz was added. If the process you implemented is indeed good, more people will contribute
”It is easier to ask forgiveness than it is to get permission” - Rear Admiral Dr. Grace Hopper
Boiled frogs: If you put a frog in a pot of normal water and then slowly increase the temperature, the frog won’t notice until it gets cooked
Programmers shouldn’t be like boiled frogs. If you notice that the project you are working on is slowly deteriorating by always keeping a big picture in mind, reverse course
Tips: 6, 7
Topic 5: Good-enough Software
Things conspire against us to prevent us from creating truly perfect software. If we focus on good-enough software, we may actually benefit from the shorter incubation time
Of course, there are tradeoffs to this. However, if you release good-enough (not sloppy or insecure) software, you can use your users’ needs to determine what needs to be built next rather than building something that is not needed at all, a true time waste
It’s not always necessary to create perfect software. In fact, the pursuit of perfect software can be quite inconsiderate for your company or your users as they just need something available
Know when to stop and ship. You need to let your code stand up for itself. If you keep refining it, you waste time and actually might create something that users don’t need. Your painting will be lost in the paint
Tips: 8
Topic 6: Your Knowledge Portfolio
Knowledge is the most important investment in your career and pays the best interest. However, it is an expiring asset so you need to constantly increase your knowledge
Knowing how to learn is the best strategic asset that a programmer could develop
Think of your knowledge as a stock portfolio. The same strategies that create great financial portfolios will also create a great knowledge portfolio:
Invest regularly: You want to continually invest in new knowledge, especially as things become outdated. Again, you want to be consistent and form a habit as that is the best way to create compounding effects (eg. it becomes easier and easier to learn new things)
Diversify: Learn different tech so that you become more useful
Manage risk: Don’t learn only conservative tech or only new tech. A mix of both will serve you well
Buy low, sell high: finding undervalued stocks is just as hard as finding undervalued tech. However, if you can be an early adopter, it will pay off handsomely. The people who learned Java just as it began were paid out very wel
Rebalance periodically: some of the things you are learning may not be worthwhile anymore, or some things you learned in the past might be more relevant again. Keep abreast of industry trends and change accordingly
Some goals to consider when it comes to building knowledge capital:
Learn a new language every year
Read a technical book every month: long-form books are still one of the best ways to grasp technical knowledge. Start off with books that relate to your current project and slowly start branching out to books that are not directly related to what you work on
Read nontechnical books to understand the human side of software
Take classes from a local uni or trade shows
Go to meetups: social learning is one of the best ways to learn new things
Experiment with different environments: try out Linux if you’re on Windows, or try Vim if you use VSCode
Keep abreast with industry updates
Once you are familiar with whatever you are learning, move on
It doesn’t really matter if you are able to use the technology in a project. Cross-pollination will occur and you will program and think differently (eg. learning functional programming will make you write OOP differently)
If someone asks you a question and you don’t know, that’s a perfect researching moment! Go and find out the answer
If you cannot find the answer, find someone who can. Be relentless. It will even help you develop your personal network
Keeping abreast of technology updates requires a lot of time. In that vein, be very purposeful of the time that you are spending and never let a second waste
Eg: if you’re in the doctor’s office and waiting, take out your e-reader or book!
However, you need to be thinking critically of everything that you read. Commercialism is everywhere! Just because it is popular doesn’t mean that it is correct nor good.
Tips: 9, 10
Topic 7: Communicate!
Communication is integral for programmers. A good idea is an orphan without good communication
Treat English like programming: put in care, use DRY and ETC principles, make sure it’s clear
Know your audience
Your communication style needs to vary if you actually what to convey your meaning. Talking deep technical things to VP Marketing doesn’t really acheive what you want
Get feedback on your communication: look at body language and verbal response. Use this to continually improve
Know what you want to say
So many developers start out their communication by just writing free-for-all. Take some time to plan out, write up an outline and ask yourself: “does this effectively communicate what I want to say, in a way that my audience would understand?”
This also applies to meetings!
Choose a moment
Plan out when you want to communicate in a way that your organizer would appreciate.
Just asking “Is this a good time to discuss about…” might be enough
Choose a style based on your audience
Some people like a TLDR, others like a wide-ranging and thorough discussion. Make sure you understand this before you plan out
However, if the communication style is not appropriate for the content, speak up and let them know. You are one half of the communication transaction
Make it look good
Even though we like to think content is king, presentation matters a lot
Make sure to use company stylesheets, headers & footers, and have proper spelling and grammar
Involve your audience
The process of writing down your thoughts is often more important than the document itself
Involve your audience through early drafts and pick their brains as you go along. This even creates greater camaraderie and invokes the Ikea effect since they had a hand
Listen to your audience
Make sure to listen to people’s thoughts and opinions throughout the presentation and incorporate them if necessary
Get back to people
It’s rude not to. Even if it’s a “I’ll get back to you later”, that’s enough
Documentation shouldn’t be low priority for development. It is integral
All the pragmatic principles that are presented in this book should apply to documentation as well
You don’t need to make documentation fancy: just putting comments in the source code is enough
However, if you are mechanically writing out comments for every single piece of code, you are violating the DRY principles. Use comments as an opportunity to explain the why of your code (eg. tradeoffs)
Don’t flame or roast others; these things are forever and will hurt you in the future
Check your recipients carefully
Keep your format simple and clear
Keep quoting to a minimum (eg. replying to a 100-line email with ‘I agree’)
Tips: 11, 12, 13
A Pragmatic Approach
Topic 8: The Essence of Good Design
A good design is one which can adapt to people and to change
ETC principle: Easy to Change
Most code design principles are just special cases of ETC: decoupling helps people change modules easier, single responsibility principle makes it easy to change if requirements change, etc.
ETC is a value, not a rule. This means that it should always remain in your head when you are making decisions
If you don’t know what the pattern of change will most likely be, you can do the following:
Make sure your code is easy to replace, so that nothing you write becomes a blocker
Take this as an opportunity to develop instinct: write it in your engineering daybook and think of possible change pathways and how you can solve for that. Come back when your code has to change and review if you guessed correctly
Tips: 14
Topic 9: DRY - The Evils of Duplication
Maintenance is a continuous part of software because change is constant
It doesn’t only start after shipping; it starts from the very beginning of the project lifecycle
Because maintenance is continuous, you may run into a situation where logic is duplicated and you have to update all logic throughout the project. This is a huge pain
DRY principle: don’t repeat yourself
Every piece of knowledge should have a single authoritative location
DRY is more than just code, it applies to everything (eg. DB schemas, documentation, communication)
DRY test: if you have to change something, how many places would you have to change the logic? If the answer is more than one, then your logic is not DRY
Not all code duplication is a DRY violation: if the code is the same but the knowledge is different (it just happens that the code is the same), then it’s not a violation
Not every function needs to have comments: if the function is written properly and has good naming, then commenting on the function is violating DRY
DRY violations can happen in data structures as well:
Ex: Think of a class that represents a line with three members start, end and length. If you think about it, storing length is a DRY violation because it requires computing the distance between start and end every single time. Make length a computed field
DRY violations are usually more common with data because of scalability concerns. It’s fine to do that as long as it’s not exposed outside of the module that requires caching
Additional tip: use getters and setters and use uniform structure across modules for that
Another very common DRY violation is API usage (both internal and external), because services are dependent upon API schemas
Ways to mitigate:
Internal APIs: use tools to generate API schemas in a neutral format that is shared across teams. It can also help you to mock APIs and functional tests
External APIs: Increasingly, public external APIs are using the same standard (eg. OpenAPI). If such a public documentation is missing, consider creating one
Duplication in data sources: use persistence frameworks to ensure that the software representation matches the data representations. Alternatively, stick the representation in a KV data structure, but you will need some extra verification (honestly didn’t understand this part)
One of the toughest duplication problems is interdeveloper duplication, where multiple developers code the exact same knowledge 6 6
Make interdeveloper communication extremely strong
Use Slack channels or other forums to resolve duplication problems
Make logic easy to reuse across the company
Try to review other people’s code occasionally, even if you’re not required to. Don’t get twisted if others review your code even if it’s not their written responsibility
Tips: 15, 16
Topic 10: Orthogonality
If two systems are orthogonal, then changes in one won’t affect another
If code is not orthogonal, it becomes really hard to change because there are interdependencies. It’s really complex
If your project is orthogonal, you get productivity and risk benefits
Productivity: you can change one thing to get one effect, you promote reuse, no overlap of work
Risk: if one module is messed up it won’t affect the rest, it’s much easier to test, you are not tightly coupled to third parties, much more secure (if there’s a security breach, it’s easy to fix up because it’s isolated)
A test for orthogonal design: if I were to change one function, how many areas in my codebase would I have to change? The answer should be one if the design is orthogonal
Try to be orthogonal to externalities (eg. phone numbers, SSN). Depending on things you cannot control is a bad idea
If you are integrating third-party vendors and libraries, ensure that integration keeps the orthogonality of your codebase
If anything, this helps you decouple third parties and allow you to switch vendors at any time
Keep your code decoupled: write shy code that doesn’t expose anything unnecessary and doesn’t need to rely on other modules
If you need the object to change state, the object itself should do that, not something else
Avoid global data and similar functions
If you do orthogonality properly, testing will be easy because it will mostly be unit testing
Fixing bugs and writing tests should be another indicator if the system is orthogonal
Tips: 17
Topic 11: Reversibility
If you make a truly DRY piece of software that is maximally orthogonal, then changing your code or reversing your previous technical decisions should be easy
Your architecture should always be flexibile: abstractions layers, breaking down code into components
Tips: 18, 19
Topic 12: Tracer Bullets
Tracer bullets are bullets that also have some phosphorus that ignite to show the bullet path. This is used by soldiers for immediate feedback on their accuracy
Engineers need to use an equivalent because we also are hitting targets in the dark: requirements are unclear, users don’t know what they want, and you’re probably working with new tech
Tracer bullets in coding is just something that gets us from requirement to some aspect of the final system quickly, visibly and repeatedly
Prioritize doing tracer bullet code for complex parts of the application where you need to determine if your approach would work quickly, especially integrations
You basically constantly test if certain things are working. This is exactly like production code and tested as well, but just not functional
Advantages:
Users get to see something early: they will be happy to see progress and they can also course correct
Developers build a structure to work in: tracer bullet programming already has a structure that devs can use
You have an integration platform: you can just add more integration code once they have been unit tested and you can test the integration
You have something to demonstrate: can help convince project brass that you are doing something
You have a better feel for progress
It should be easier to change things in your code with tracer code development as well as gathering feedback faster
Tracer bullets are different than prototyping: prototypes are meant to be thrown away, but tracer bullets will be part of the skeleton of the final system (i.e. they are permanent)
Tips: 20
Topic 13: Prototypes and Post-it Notes
Prototyping is a learning exp erience. You’re trying to de-risk something and you are willing to throw the prototype away
You can ignore corectness, completeness, robustness and style. All you care about is whether your prototype validates a certain approach
For architecture, you can just use Post-it notes and index cards toe dtermine if the code is well-structured and DRY
Make sure to make it clear that prototypes are not for production
Tips: 21
Topic 14: Domain Languages
Your choice of language actually influences how you think about a problem. If the language is ill-suited to the problem space, it will be harder to think of solutions
Internal languages: embedded into your code and are extensions to you code’s vocab (eg. axios for JS)
Pros: you can make use of the language’s power since the internal library or modules you are using are written in the same language
Cons: it is limited by the language itself
External languages: they are outside of your code and do not use the code vocabularly. They are read by your code and translated into a form you can use (eg. Cucumber)
Pros: you can customize to your problem space
Cons: you need to write a parser and it’s sometimes more effort than it is worth
If you really want to use an external language, use an off-the-shelf language like YAML, JSON or CSV
Never spend more effort than you save
Tips: 22
Topic 15: Estimating
Estimating is important to understand the feasibility of certain designs
When you convey your estimates, you want to ensure that you account for accuracy. The farther out the estimate, the less granular your estimate should be
How to build estimates for your work?
Ask around with people who have done it before so you can leverage their experience to understand the complexity of the task
Make sure you understand the scope of the project really well before you start estimating
Build a mental model of whatever you are estimating. You can create simplicity trading off for accuracy
Break the model into sub components and figure out the mathematical relationship between each module
Place values for parameters. Note that some parameters can affect the final estimate a lot more, so spend more time justifying your values. Try to avoid creating parameter estimates based off other estimates, as it build up on error
Calculate: do a bunch of calculations to get the right error range. If answers seem off, then your model is off
Keep track of your final and subestimates, as you can use it in the future to improve
To do project estimation, you can either provide your estimation across a range of plausible scenarios or you can just start the project and as you iterate on it again and again, you can create confident estimates
Tips: 23, 24
The Basic Tools
Topic 16: The Power of Plain Text
Authors advocate for keeping everything in plain text and understandable to humans
Why plain text?
Insurance against obsolescence: human-readable and self-describing data will outlive all other forms of data
Leverage: everything can operator on plain text
Easier testing: You don’t need any special tools to change your testing
Tips: 25
Topic 17: Shell Games
The workbench of a programmer is the shell. You can do a lot with it
Shell vs. IDE: the authors believe the shell is better. You can use the IDEs for certain tasks, but the shell gives a lot more flexibility, like automations, custom macros, etc.
GUIs limit you to the functionality that the designers intended
If you become really good with your shell, you can become a productivity powerhouse
Customize the shell: change the color schema, configure the prompt, create aliases, and command completion
Tips: 26
Topic 18: Power Editing
Try to be a master at your editors such that the time it takes to have a thought and to actually action on the thought in the editor is short as possible
Challenge list to determine if you are fluent:
Moving and making selection by character, word, line and paragraph
When editing code, move by various syntactic units (matching delimiters, functions, modules, …)
Reindent code following changes
Comment and uncomment code blocks with a single command
Undo and redo changes
Split the editor into multiple panels and navigate between them
Navigate to a particular line
Sort selected lines
Search for both strings and regular expressions and repeat previous searches
Temporarily create multiple cursors based on selection or pattern match and edit the text at each in parallel
Display compilation errors in the current project
Run the currnt project’s tests
Can you do all of the above without needing to use the mouse/trackpad
If you find yourself doing something repetitive in the editor, it’s time to look for a shortcut. Once you have found it, you need to use it through repetition (ideally multiple times a day)
Look around for extensions to help improve your editing
You may want to look into the extension language to create your own automations or even full-blown extension (publish it if you do)
Tips: 27
Topic 19: Version Control
Version control is massively useful and keeps a history of your project
Usually, people are developing in repos using branches to keep things isolated. Think of branches as isolated developer islands that a developer can use to do whatever they want
Good teams use version control and a centralized repository. you can even automate build and testing upon pushing to the repo
Tips: 28
Topic 20: Debugging
Since no one writes perfect software, debugging is almost always a huge part of any developer’s day
It might be cathartic to blame others for bugs, but it really does nothing. Fix the problem instead of complaining
Debugging mindset: make sure you are calm, turn off any ego protection or project pressures
Don’t think “this bug is impossible” because it is possible and it did happen
Think about treating the root cause and not the symptoms
To start debugging, make sure the code is built cleanly and the bug report clearly indicates the source of the bug
If necessary, look at how the user triggered the bug (you might want to watch)
Debugging strategies
Reproduce the bug and try to create a test for it. It often gives you an idea of how to fix the bug
Read the actual error that is causing the bug
If the bug is results in a bad result, use your debugger to track it down. Move up and down the call stack and note down where you went debugging. For large call stacks, use binary search to find the bug faster
If the bug is related to bad input data, get a copy of the input data and use binary search to figure out which part of the data is causing the error
If one of your previous releases suddenly fails (hopefully you have regression tests to catch this), use binary search to figure out which release introduced th bug
Binary search:
Stack frame trace: choose the middle and see if the error is there. If it is, focus on the frames before. Otherwise, check in the frames after
Bad input data: divide the data into two and see if the error can be isolated to one half and keep doint it
Releases: test the half-way release on your failing test. If it passes, focus on releases after. Otherwise, look at releases before
Use tracing comments and plop it into logs for easier state recreation when hunting down bugs
Use rubber ducking to go through the offending code and problems; sometimes the answer leaps out by simply explaining it to yourself
Use a process of elimination to figure out the most likely causes of the bug. It’s very unlikely that the library itself is incorrect; it’s far more likely that you are calling the library incorrectly
If you changes one thing and it causes or solves the bug, it’s very likely that it is the root cause, regardless of how unlikely it sounds
If you feel surprised by a bug, then it means you have too much trust in your code. You will need to evaluate all of your assumptions and carefully consider boundary conditions
Don’t gloss over a routine or piece of code believing that it is working. Prove it and test out the boundary conditions in the context of the bug
Consider why these surprise bugs weren’t caught before. You may want to revamp a whole bunch of other tests or improve your assertion programming
Think about how you can easily catch these surprise bugs in the future and inform your team if there was a broken assumption
Debugging checklist:
Is the problem being reported a direct result of teh underlying bug or a symptom?
Is the bug really in the framework you’re using? Is it in the OS? Or is it in your code?
If you explained this problem in detail to a coworker, what would you say?
If the suspect code passes its unit tests, are the tests complete enough? What happens if you run the tests with this data?
Do the conditions that caused this bug exist in any other parts of the system? Are there other bugs still in its larval stage, just waiting to hatch
Tips: 29, 30, 31, 32, 33, 34
Topic 21: Text Manipulation
Text manipulation languages are often unwieldy to use, but are incredibly powerful
Examples: awk and sed for Unix and Python + Ruby for more structure
It’s often really easy to mock up prototypes in text manipulation languages and you can do it very quickly
Tips: 35
Topic 22: Engineering Daybooks
A daybook is a kind of journal that records what a person did during the day, things they’d learned, sketches of ideas, readings
Useful for taking meeting notes, helping out with debugging and reminders
It’s more reliable than memory, gives you a place to store non-relevant ideas, and acts as a great rubber duck
Try writing it out instead of typing it up
Pragmatic Paranoia
Like driving, we need to practice coding defensively. Even more, we need to be wary of ourselves and code in defenses against our own mistakes
Tips: 36
Topic 23: Design by Contract
DBC started with the language Eiffel. If summarized into one sentence, it would be: if all the routine’s preconditions are met by the caller, the routine shall guarantee that all postconditions and invariants will be true when it completes
If either party fails to live up, a pre-agreed remedy is invoked (eg. exception, termination)
Failure to live up to a contract is a bug! This is why invalid user input is a bug, even though it probably violates the contract you set in the code
Write lazy code: strict preconditions but lax postconditions and invariants
Using DBC will force you to think regardless of which paradigm you are using
DBC is a different approach to the issue of program correctness: requires no mocking, exhaustive, continuous, checks invariants and also may be more efficient
How to implement DBC:
Start off by thinking of the pre and postconditions as well as the invariants. Think about the boundary conditions and ranges of inputs your routine can handle
Assertions can help, but suport is limited
Crash as early as possible at the problem site rather than giving out special values if a certain precondition or invariant is violated
Semantic invariants: certain requirements cannot be violated despite management changes (eg. never duplicate transactions). These are useful in designing your code
Tips: 37
Topic 24: Dead Programs Tell No Lies
Don’t discount the errors that are happening in your program. In fact, errors can be extremely useful checks in your logic
If you keep try, catch and raising errors, your code will be huge. Some programmers do this
try do
add_score_to_board(score)
catch e as InvalidScore:
...
catch e as BoardServerDown:
...
Instead, leave error handling to the function/module! It keeps things less coupled and things are automaticaly propagated
Leaving things to crash is great because you prevent any more damage from taking place and you can get as much conetxt as possible if logged correctly
Use supervisors for appropriate crash handling if it is important in your situation. You might even develop supervisor trees for crash handling (supervisor of supervisor handles crash of a supervisor)
A dead program does a lot less damage than a crippled program
Tips: 38
Topic 25: Assertive Programming
Don’t deceive yourself in thinking certain things can’t happen. Write code to check the assumption (just use asserts)
Asserts don’t replace error handling. Asserts are for assumption checking!
You may want to catch the assertion exception and perform a particular operation, or you may want to log something
Due to the fact assertions crash programs, make sure you don’t have any weird side effects (eg. moving too far in a loop and crashing)
People often assume that assertions can be turned off in production due to overhead
Assertions are incredibly useful in detecting weird or buggy situations. Leaving it in production is often a great practice
Tips: 39
Topic 26: How to Balance Resources
When you are using resources (eg. threads, memory, files), make sure to clean up after yourself. In fact, whatever is responsible for allocating should also be responsible for deallocating
Use parameters to pass around allocations but never try to decouple the allocation and deallocation process of something
Some languages (like Python) have context manager blocks that only do certain things when a file is open and then closes once the scope is finished. Reducing scope for allocations can save you a lot of headache
If you have applications which require more than one resource allocations, use the following guidlines:
Deallocate in opposite order of allocation
If allocating the same resources across functions, use the same order to prevent deadlock
If language is OOP, use class encapsulation to control allocation and deallocation
With languages that use exceptions, you may run into an issue of never deallocating a resource since you fired an exception:
Can either utilize variable scope or use a finally block in a try...catch.
The issue with this antipattern is if you fail to allocate, you will deallocate a nonexistant variable. Do allocations outside of the try block if dealing with exceptions
Sometimes you have items that are allocated for some time and you can’t really deallocate when scope is finished. Make sure you establish a semantic invariant on who is responsible for deallocation
Try to build code that checks if you are actually freeing resources in case you made a mistake somewhere and it is silently remaining
Tips: 40, 41
Topic 27: Don’t Outrun Your Headlights
Cars often crash because they are not able to react to what is in their headlights because they were moving too fast
Always take small and deliberate steps while checking for feedback and adjusting before you proceed
As soon as you feel that you are doing something that is akin to fortune-telling (i.e. designing for a future integration), then stop!
A better approach is to make your code DRY, cohesive and decoupled so future changes are easy. Much better than coding for the future
Remember black swans: improbable things are more likely than you think
Tips: 42, 43
Bend, or Break
With the frantic pace of change, the code one writes should be flexible enough for future change. If it’s too brittle or unwielding, you will land in trouble
Topic 28: Decoupling
Coupling is the enemy of change, as you will need to change several things in order to change something. You want individual components to be as coupled to as few things as possible
Avoid coupling because it is transistive: if A is lightly coupled but B is heavily coupled, pairing A and B will make A also heavily coupled
Symptoms of coupling:
Dependencies between unrelated modules
Simple changes to one module propagate through unrelated mmodules or cause breaking in other parts of the system
Developers are afraid to change code because they are afraid of what will be affected
Meetings where everyone needs to attend because no one is sure who will be affected
Train-wreck code: code that has a whole bunch of chained method calls
Problem: chaining method calls contains a lot of implicit knowledge on the structure of data between each call. Each call is also dependent on each other
The issue with this is a fundamental lack of encapsulation. Methods shouldn’t know internal structure, it should just act as if it doesn’t know!
Tell, don’t ask: leave the decision of whether the function call was bad or not to the function. You don’t need to add extra logic outside through a method chain
Think about the Law of Demeter: methods should only call other methods in the class, parameters, methods in the objects it creates
Try not to access something in a chain
Creating function pipelines is fine because it doesn’t rely on hidden details
Globalization: kind of obvious how this creates coupling
Global data includes singleton and external resources (eg. database, datastore, service API). Make sure you provide proper interfaces for reusability and decoupling
Inheritance adds more coupling (discussed in Topic 31)
Ultimately, keep your code shy so it only deals with things it knows directly about
Tips: 44, 45, 46, 47, 48
Topic 29: Juggling the Real World
Writing code that responds to events is often a much better paradigm for you and for users
4 strategies to help write event-based code: finite state machines, Observer pattern, publish/subscribe and reactive programming
Strategy #1: finite state machines
You can define states for your code and define the events that control the movement from one state to another
You can easily create tables for your state machines and you can use it directly in your code
You can store a representation of your state machines and use it elsewhere
Strategy #2: Observer pattern
Basically have one module notify all of it’s subscribers. Each subscriber registers to be notified in the notifier class
It’s pretty decent but still introduces coupling problems (subscribers need to register for notifications) and also may create performance bottlenecks
Strategy #3: publish/subscribe
Almost like Observer pattern except there is a channel that subscribers can subscribe and notifiers can push to.
Very common (eg. usually most cloud providers and languages support this paradigm) and decouples the messaging part of Observer pattern
Strategy #4: reactive programming, streams and events
React is a great example of reactive programming: if an event happens, the DOM updates
You can take this to the next level by having streams of events, which is great for joining async and sync code (tbh the book didn’t do a great job explaining this)
Topic 30: Transforming Programming
If you really think about it, our software is just transforming data. We should keep this in mind as we program
Break down your input and output into pipelining steps and string together
This is often better than encapsulating in classes because it is much easier to change pipeline steps than changing interfaces
You should create some sort of wrapper for this data to check if the data between pipeline steps is actually valid. If it isn’t, the steps will just forward the error
Tips: 49, 50
Topic 31: Inheritance Tax
Programmers have used inheritance for two reasons: either they hate typing so they create parent classes to abstract common behaviour, or they love types and subclass
Problems with inheritance:
Coupling: if the parent class changes behaviour or the API, the child classes need to be changed as well
Complex typing: it becomes really hard to figure out what type of object something is if you have a monstrosity of an inheritance tree
Alternatives:
Use interfaces or protocols: interfaces or protocols basically define behaviour but provides no code (abstract virtual classes). You get polymorphism without inheritance
Delegation: if you use inheritance, a lot of methods from the base class is carried over to the child class and those are callable. If your child class is only using a few methods from the base class, this is overkill. Just delegate the implementation of those methods to the child class and get rid of the inheritance
Mixins and traits: Mixins basically extend classes/objects with additional functionality without using inheritance. If you make these specific enough, you get the added benefit of new functionality without needing to inherit from a class
Inheritance is like asking for a banana but you get a gorilla with the banana along with the entire jungle. Be wary of using inheritance, it’s rarely the cleanest answer
Tips: 51, 52, 53, 54
Topic 32: Configuration
If code relies on values that may change after the app has gone live, keep those values external (eg. environment values, customer-specific values, credentials)
You can keep the cvalues in a plain-text format like YAML or JSON or in a database
Recommend that you read in the configuration behind a thin API to decouple the configuration implementation from the code
If you make configuration a service API, then you can do a lot of interesting things:
Multiple apps can share configuration info with different access controls
Configuration changes can be made global
You can maintain configuration via UI
You can make config changes dynamic: you wouldn’t need to restart your entire service to change one config value. You can just use a pubsub model to push the change to every service that uses the configuration
Configuration allows you to be extremely flexible. Don’t overdo it and configure everything
Tips: 55
Concurrency
Concurrency is when execution of two pieces of code act as if they run at the same time. You need to code in an environment that can switch execution between different parts of your code using fibers, threads and processes
Parallelism is when execution of two pieces of code actually run at the same time. This depends on having hardware that can do multiple things at once, like multiple cores or CPUs
Concurrent and parallel code is extremely common now because our hardware can support it. It makes things extremely quick
Topic 33: Breaking Temporal Coupling
Temporal coupling is when your code dictates a set order or waiting when you don’t really need to. It’s pretty inflexible and unrealistic. Concurrency removes this coupling
To start off with any project, think about what can happen at the same time and what needs to be happen one after the other
Use an activity diagram to identify activities that can be performed in parallel
To identify opportunities for concurrency, look for activities that would probably stall your code and see if you can do something else in the meantime
To identify opportunities for parallelism (assuming you have the hardware capabilities), look for independent streams of work then will be combined at some point
Tips: 56
Topic 34: Shared State Is Incorrect State
The problem that programmers often face with concurrency is shared state. If two processes are using the same state, then you can’t be sure if your state is consistent, as the state could be changing as you are looking at it. It is nonatomic
The solution: you want to make each operation atomic, where the value cannot change while you are using it
In code, we use semaphores, which are simply things that only person can have. If you use semaphores as access to values in a concurrent program, then only one program can use the value at a time
A good example is the classic ‘you can only talk if you hold ‘. The object is a semaphore and you can only access the value (your ideas) if you hold the semaphore
Topic 35: Actors and Processes
Actor: independent processor with own local, private state. Has a mailbox and only processes messages when it arrives in mailbox. Otherwise, it sleeps
When processing, can spawn other actors, send messages to other actors or change local state
Process: general purpose processor implemented by OS to help concurrency. You can constrain this to act like actors, which is expecte
Note the following:
Nothing is in control or scheduling what happens next
The only state in the system is the messages and the local state of each actor
There’s no concept of replying to messages. If you want reply behaviour, you include the mailbox address in the message so it can be eventually responded to
An actor will process each message to completion and only processes one at a time
Because of the above, actors execute concurrently, async and share nothing
Just start off the system with some initial state and let each actor know about each other and you can just run without writing too much specific code to handle concurrency
Tips: 59
Topic 36: Blackboards
The blackboard concept comes from solving crime scenes where all the evidence is posted on a blackboard by different detectives
The detectives don’t need to know about the existence of other detectives, they only care about the blackboard
Detectives can be in different disciplines and can come and go, all that matters is they use the blackboard to collectively solve the case
There are no restrictions on what is placed on the blackboard
Blackboard-type concurrency is often really useful for very intractable and messy concurrent problems
An example in modern day programming is Kafka
With blackboard, actors and more, it can get harder and harder to figure out what is actually going on
Tip: use tracer IDs for certain actions and append this to a log file that can be dumped
Tips: 60
While You Are Coding
One of the biggest reasons why projects fail is because people believe that the act of coding is simply mechanical. It actually requires a lot of thought
Topic 37: Listen to Your Lizard Brain
Your lizard instincts are actually useful. Notice instinctual behaviour and figure out why it’s coming up
Fear of starting usuaully stems from unease with something or the fear of making mistakes:
If having a sense of unease, figure out what is cauing the unease (might take some time) and then act on it
If fearing a mistake, remember that coding is not the only show of competence
If you are having a hard time coding, then it’s likely that something is wrong with the code. Listen and act
If you feel something off, do the following:
Stop what you are doing and do something else mindless away from the keyboard and try to wait for insight
If you’re still stuck, try externalizing the issue, draw it out, explain to a rubber duck, etc.
If still stuck, tell yourself that what you’re going to do has no impact and just prototype
Prototyping is kind of like play. It can help you understand what was bugging you before hand or can help you settle the unease
When you are stuck with other people’s code, try identifying patterns and why they wrote code the way they did
Tips: 61
Topic 38: Programming by Coincidence
There are minefields everywhere we code. We shouldn’t be programming by coincidence (relying on luck and accidental success) - we should be relying on programming by deliberation
Example of programming by coincidence: programming a bit → tries → works → programming another bit → tries → works
When the program fails, you won’t know why because you don’t know why it worked in the first place
Another example: using APIs in the wrong way because the API response is broken
Another example: doing something wrong (and you know it’s wrong) but you get the right answer. You decide to leave the wrong stuff behind
This is bad for several reasons: it might still not work, or you are relying on something that will change based on other factors, it’s slow, introducing bugs
Never use undocumented behaviour for routines you are calling
Rules of thumbs to correct broken things are terrible. Fix the problem instead of putting a bandaid that might make things worse
Don’t fall prey to phantom patterns (bc correlation doesn’t imply causation). If you suspect a pattern, prove it
Don’t rely on certain contexts for your program (eg. GUI prescense, English input, file presence, server accuracy)
How to program deliberately:
Always be aware of what you are doing
Can you explain your code?
Don’t code with something you don’t fully understand. If you’re not sure why it works, you won’t be sure why it fails
Proceed from a plan
Rely only on reliable things. Don’t depend on assumptions
Document your assumptions
Don’t just test your code; test your assumptions
Prioritize your effort
Don’t be a slave to historical decisions
Tip: 62
Topic 39: Algorithm Speed
Use Big-O to determine algorithm speed, especially when working with loops or recursion
If you are working on code that is not bounded in terms of input, it is worth thinking about Big-O and if there is anything you can do to bring the asymptotic runtime down
Try to estimate your estimates by running in production-like enviornments and benchmarking performance
Be wary of making things overly complex when something simple but inefficient can also do the job
Be wary of premature optimization. You only really should consider spending resources to improve efficiency of the algorithm if it is the bottleneck
Tips: 63, 64
Topic 40: Refactoring
Code is meant to change over time, so refactoring is totally normal
Refactoring is a disciplined activity (not free-for-all) that does not add any additional features or functionalities. It’s a restructuring event
Refactoring shouldn’t be a special thing; it shuld be happening nearly daily with low-risk steps (eg. not plowing, more like weeding and raking)
Refactoring should occur whenever you have an updated state of knowledge and you realize you could have reworked things differently
Examples of refactoring triggers: duplication, non-orthogonal design, outdated knowledge, new usage requirements, performance, your tests pass and you can go back and clean
It may be a painful process, but it will help out in the long run if you address small chunks. You can avoid big rewrites and other disruptions
If you don’t refactor without a plan, it will just cause more problems than you started out with. Tips to refactor without doing more harm than good:
Don’t try to refactor and add functionality at the same time
Make sure to have good tests before you begin refactoring and run as often as possible to detect if changes are breaking anything
Take short, deliberate steps and test after each. It will keep you from having a long debug session
Note that IDEs now have a lot of great functionality to help automatically refactor your code
Tips: 65
Topic 41: Test to Code
Benefits from testing don’t come from finding bugs, but rather from the thinking and writing of tests
Start by thinking about what you change should be accomplishing and how you would go about testing it. It will open up a lot of things that you should consider when coding the change
It can help you avoid coupling, nonorthogonal designs, inflexible code, considering boundary conditions and contracts and more
Test-driven development:
Decide on what you want to change → write a test that will pass once the functionality is implemented → ensure that the test you wrote is the only failing test right now → write the smallest amount of code to get the test to pass → refactor and check if tests still pass
The above cycle should be really short (few minutes). Great for beginners in testing
Don’t become slaves to TDD and spend all your time getting to 100% code coverage, redundant tests and working from the bottom. Look at the big picture and whether you are actually solving the problem
There is confusion around whether code should be written in a top-down manner (get the requirements and break it down) or bottom-up manner (build abstractions and work yourself up)
Both methods are wrong, You should build software incrementally by adding end-to-end functionality and learning about the requirements as you go
Start with the destination and use TDD to get yourself there
A goal that we want as software engineers is to just use components as building blocks, but that requires reliability of each building block/component/module.
To do this, use unit testing: tests on each module in isolation. Creates a fake environment and tests routines in a module against known values or past values from regression tests
If we can make sure each component is properly tested, then we can just combine and test the system as a whole
How to write unit tests:
Test against a contract of a module across a wide variety of test cases and boundary conditions
Include any ad-hoc testing that you used when you were coding
In production environments, make sure your logging is sound and easy to parse. Also try to create a diagnostic switch so developers can see status messages and such for a particular user
Everything you write will be tested, so best to test it thoroughly rather than later and let users test it
If you are serious about testing culture, then every test should pass. As soon as you excuse one test from not passing, a vicious spiral begins. Don’t leave broken windows
Code tests just how you would code anything else: DRY, not relying on coincidence and luck, clean and robust
Tips: 66, 67, 68, 69, 70
Topic 42: Property-Based Testing
One issue with unit tests is that a faulty assumption can make it’s way both into code and into tests
Property-based testing: test out contracts and invariants
Eg: a sorting function should not change the length of the list and it should actually sort. You can then generate a whole bunch of input based off these assumptions and test them out
In Python, you can use the hypothesis package along with pytest to test out your code based off properties and automates a lot of testing for you
It can be frustrating to do property-based testing because it might be tricky to pin down what caused a test to fail since it passes random values into your function according to the constraints that you set
If something fails, take the inputs and make a separate unit test. This makes the falsifying test persistent and acts as a regression test
Just like testing, property-based testing can help you code better as you have to think about the invariants in your code
Tips: 71
Topic 43: Stay Safe Out There
There’s a growing number of cyber attacks. Developers have to be paranoid about security now
After you’re done coding and testing, you need to consider how someone could maliciously destroy your system
Basic security principles:
Minimize attack surface area: you want to provide as few routes as possible for attackers to actually attack
Code complexity increases surface area, as there could be a lot of unanticipated side effects. Simpler code is easier to make secure
Any input data could be tainted, so always make sure to santize
Unauthenticated services can create DOS opportunities at the very least. If data is available in unauthenticated places, you’re finished
Authenticated services could pose a threat later, so make sure to regularly cull inactive authenticated users. If certain users are compromised and used the same password as on your service, you will also be compromised
Don’t give away unneeded information to the end users. Obfuscate if necessary
Don’t let debug info be given away either. Keep stack traces and logs secure, as it can give an attacker a better understanding of your weaknesses
Principle of least privilege: use the least amount of privilege for the shortest time you can
Make your user roles more granular to allow for least privileged access
Secure defaults: the default state of something should be the most secure state. Anything else to improve accessibility or UX should be opt-in
Encrypt sensitive data: don’t check in secrets into version control and hash anything that could be sensitive
Maintain security updates: it might be annoying to update security, but it is essential. The largest breaches were due to a lack of updating
Don’t limit what can be put into a password field (eg. allow for special characters, pasting, mandate certain compositions of characters). You want long, random passwords high entropy/ Limits actually encourage bad password habits
Don’t try to do crytography by yourself. Rely on some other reliable, preferably open-source method
Tips: 72, 73
Topic 44: Naming Things
Naming is about intent. Ask yourself what you are planning to do with this new piece of code and name according to that
Name according to the culture of the language: loop variables, snake vs camel case, Unicode
You want project vocabularly to be widespread and consistent in your team
Encourage lots of communication, add a glossary
Don’t let yourself fall in misleading names. Change as you go
If you can’t rename, that indicates you have a design problem. Fix the design problem and change the name
Make renaming as easy as possible
Tips: 74
Before the Project
Topic 45: The Requirements Pit
Requirements rarely lie on the surface. People need to dig beneath assumptions, misconceptions and politics to get the actual requirements
Requirements are rarely static because the world is not static. Programmers need to help people understand this and help them get what they actually need
When you get a request, think about why it was being asked and if there are any edge cases that weren’t considered. Ask the client about these edge cases and present options
Requirements are continuously refined and people start to arrive at the actual requirements
We need to give the opportunity for feedback on requirements to occur with clients: talk through consequences, let them play on a prototype, etc.
To really understand clients, become a client yourself and understand what they are going through
When a certain policy (but not requirement) is given, implement the general case and make it easy to change in case the policy changes
Requirements should really only be documented in code. Any other documentation shouldn’t get a signoff because it is constantly changing
Clients almost never read requirement docs
You can be much more agile and put requirements in user stories
Requirements should be broad enough to allow flexibility in implementation. It should be all about problems to solve and needs
Beware of scope creep
Maintain a project glossary so everyone understands what each other is talking about
Tips: 75, 76, 77, 78, 79, 80
Topic 46: Solving Impossible Puzzles
You may run into really tough problems that you are unsure about solving. The trick is to look carefully at the requirements and determine if they are absolute or preconceived
Enumerate all the possible avenues, even onese tthat you don’t think will work. Go through the list and explain why each path wouldn’t work
If you are hitting roadblocks, go do something else and come back to the problem or rubber duck it
Be sure to note down what works and what doesn’t in your daybook and never panic
Tips: 81
Topic 47: Working Together
The way an organization communicates is often reflected in the code, so be wary of that
Pair programming is a powerful technique for programmers to work together
Mob programming is taking it to the next level with more people thinking and a few people coding
Pay attention to the rules of pair and mob programming so no one person is overwhelmed. Do retros to improve
Tips: 82
Topic 48: The Essence of Agility
A lot of teams have lost the meaning of agility and just put more processes and management in place without any sort of continuous feedback
You cannot do item X and suddenly be agile, because being agile is in response to change
Agile means working out where you are → making a meaningful step where you want to go → evaluate and fix
This trickles down all the way into coding
Tips: 83
Pragmatic Projects
Topic 49: Pragmatic Teams
Pragmatic teams are small (< 10-12), little churn, high trust and reliability within the team
Everyone should be concerned about leaving broken windows (poor code) behind and should encourage this philosophy among others
Everyone should actively monitor the environment for changes so you don’t get boiled like a frog
A pragmatic team will schedule time for improvement and innovation
The team should have a distinct, clear and well-organized style of communication. Work on building up a team identity
The communication should be frictionless so people don’t accidentally work on the same thing
Use team-wide e2e building and tracer bullet development to get everyone on the same page and iterate quickly
Automate anything the team finds difficult. Make sure any team has the means to automate and build tools
Beware of adding too much paint to the project. Sometimes, the team should be ok with good-enough
Tips: 84, 85, 86
Topic 50: Coconuts Don’t Cut it
Investing in the artifacts of something but not actually doing what it is supposed to do is known as a cargo cult
Eg: having standups and retros is not a symbol of being agile. You actually have to be agile
Very popular currently to copy what the big companies are doing, but you have to consider the market and constraints of what you are building and whether those companies are trying to solve the same problems
Do what works and be pragmatic about it
What you want to do is to take the best pieces of methodology and adapt it to your circumstances to a point where you can deliver value to users almost continuously
This requires rock-solid infrastructure and team-wide understanding of work organization
Don’t invest too much in any alternatives. Be nimble
Tips: 87, 88
Topic 51: Pragmatic Starter Kit
Version control:
Keep everything needed to build the project under version control. This allows machines to be ephemeral and you can easily scale up or down
Builds, tests and deployments should be triggered via commits or pushes and built in containers in the cloud. Releases can then become near continuous
Ruthless and continuous testing:
Use testing to catch bugs. You should be testing as soon as you write code
It also gives a useful benchmark when you are finished, as tests need to run perfectly for you to be done
Test environments should match production environments closely. Testing should also be triggered automatically
Types of testing:
Unit testing: exercises a particular module. It is the foundation
Integration testing: tests how systems work together. Should be easy if you have good contracts. Otherwise become huge sources of bugs
Validation and verficiation: test if what you built meets the requirements and use cases for the end user
Performance testing
Testing the testing: when you are writing a test to detect a bug, make sure the test actually complain. Occasionally introduce bugs in a separate branch and verify the complaining
What you really should be worried about is covering all the different states that a module may need to handle, not covering all the code. Property-based testing is great for this
Add a test for a bug every single time you find one so you can ensure that bugs only happen once
Automation
Manual procedures are breeding grounds for mistakes. Use scripts as much as you can
Tips: 89, 90, 91, 92, 93, 94, 95
Topic 52: Delight Your Users
Your goal as a SWE is to delight users. You’re measured on business objectives related to users, not technicalities
Once you have understood the metrics of success for your project, think critically about how to move towards the goals and if everyone is aligned on this
As your knowledge of the domain and code increases, you can probably find ways to deliver user value more efficiently than what is laid out in requirements
Tips: 96
Topic 53: Pride and Prejudice
Seek pride in ownership of code and respect others’ code
This shouldn’t devolve into territorial ambitions with code
Your name on the code should ring professionality
Tip: 97
Tips
Tip 1: care about your craft
Tip 2: Think! About Your Work
By thinking critically about all your work as you do them, you will develop a feel of mastery around programming
Tip 3: You have agency
Use your agency to improve yourself and your organization
Don’t keep trying though if you keep hitting barriers in your organization. You can either change your organization or change your organization
Tip 4: Provide options, don’t make lame excuses
If you feel that something is a bad idea or can’t be done, take some time to think how that excuse would sound to others
If you can’t find a way, come up with solutions instead of bringing problems
Tip 4.1: If you find yourself saying “I don’t know”, follow up with “-but I’ll find out”. You are taking responsibility like a pro
Tip 5: Don’t leave broken windows
Don’t ever leave your code or project in a bad state. It sets a precedent of not caring that will accelerate throughout the code base
Tip 6: Be a catalyst of change
If you feel that something is worth changing, do it! If you can demonstrate the utility and show it to others, it may be picked up by the company
Tip 7: Remember the big picture
To avoid the boiled frog situation, constantly keep yourself aware of the state of the project and remember the final goal. If you even feel an inkling that things are slowly deteriorating, raise alarms
Tip 8: Make quality a requirement issue
Good software today is often better than perfect software tomorrow, especially because requirements change
Ask your users what is necessary and what kind of quality they are looking for. More often than not, they don’t care too much about perfection.
Tip 9: Invest in your knowledge portfolio regularly
Tip 10: Critically analyze what you hear and read
Ask the five why’s, follow the money, always understand the context of what you are reading, consider when and how whatever you are reading would be useful, and why it is a problem in the first place
Tip 11: Treat English as a programming language
Tip 12: It’s both what you say and how you say it
If you can level up your communication styles, you can become infinitely more influential
Tip 13: Build documentation in, don’t bolt it on
Tip 14: Good design is easier to change than bad design
Tip 15: DRY - don’t repeat yourself
Tip 16: Make it easy to reuse
If you make your code/module easy to reuse, then you won’t fall into interdeveloper duplication problems
Tip 17: Eliminate effects between unrelated things
Tip 18: There are no final decisions
Tip 19: Forgo following fads
Tip 20: Use tracer bullets to find the target
Tip 21: Prototype to learn
Tip 22: Program close to the problem domain
Tip 23: Estimate to avoid surprises
Tip 24: Iterate the schedule with code
Tip 25: Keep your knowledge in plain text
Tip 26: Use the power of command shells
Tip 27: Achieve editor fluency
Tip 28: Always use version control
Tip 29: Fix the problem, not the blame
Tip 30: Don’t panic
Tip 31: Failing test before fixing code
Tip 32: Read the damn error message
Tip 33: “select” isn’t broken
Tip 34: Don’t assume it - prove it
Tip 35: Learn a text manipulation languages
Tip 36: You can’t write perfect software
Tip 37: Design by contracts
Tip 38: Crash early
Tip 39: Use assertions to prevent the impossible
Tip 40: Finish what you start
Tip 41: Act locally
Tip 42: Take small steps - always
Tip 43: Avoid fortune-telling
Tip 44: Decoupled code is easier to change
Tip 45: Tell, don’t ask
Tip 46: Don’t chain method calls
Tip 47: Avoid global data
Tip 48: If it’s important enough to be global, wrap it in an API
Tip 49: Programming is about code, but programs are about data
Tip 50: Don’t hoard state, pass it around
Tip 51: Don’t pay inheritance tax
Tip 52: Prefer interfaces to express polymorphism
Tip 53: Delegate to services: has-a trumps is-a
Tip 54: Use mixins to share functionality
Tip 55: Parameterize your app using external configuration
Tip 56: Analyze workflows to improve concurrency
Tip 57: Shared state is incorrect state
Tip 58: Random failures are often concurrency issues
Tip 59: Use actors for concurrency without shared state
Tip 60: Use blackboard to coordinate workflows
Tip 61: Listen to your inner lizard
Tip 62: Don’t program by coincidence
Tip 63: Estimate the order of your algorithms
Tip 64: Test your estimates
Tip 65: Refactor early, refactor often
Tip 66: Testing is not about finding bugs
Tip 67: A test is the first user of your code
Tip 68: Build end-to-end, not top-down or bottom-up
Tip 69: Design to test
Tip 70: Test your software, or your users will
Tip 71: Use property-based tests to validate assumptions
Tip 72: Keep it simple and minimize attack surfaces
Tip 73: Apply security patches quickly
Tip 74: Name well; rename when needed
Tip 75: No one knows exactly what they want
Tip 76: Programmers help people understand what they want
Tip 77: Requirements are learned in a feedback loop
Tip 78: Work with a user to think like a user
Tip 79: Policy is metadata
Tip 80: Use a project glossary
Tip 81: Don’t think outside the box - find the box
Tip 82: Don’t go into the code alone
Tip 83: Agile is not a noun; agile is how you do things
Tip 84: Maintain small, stable teams
Tip 85: Schedule it to make it happen
Tip 86: Organize fully functional teams
Tip 87: Do what works, not what’s fashionable
Tip 88: Deliver when users need it
Tip 89: Use version control to drve builds, tests and releases
Tip 90: Test early, often and automatically
Tip 91: Coding ain’t done ‘til all the tests run
Tip 92: Use saboteurs to test your testing
Tip 93: Test state coverage, not code coverage
Tip 94: Find bugs once using tests
Tip 95: Don’t use manual procedures
Tip 96: Delight users, don’t just deliver code
Tip 97: Sign your work
Tip 98: First, do no harm
Tip 99: Don’t enable scumbags
Tip 100: It’s your life. Share, celebrate and build. Have fun!
Worthwhile Exercises
Topic 6: Your Knowledge Portfolio
Start learning a new language in a domain that you don’t frequent
Start reading a technical book and vary the styles (eg. if you do a lot of designing, read some books about practical coding)
Talk shop with a few individuals: look for people in your company or go to meetups
Topic 7: Communicate!
Book recommendations
The Mythical Man-month
Peopleware
Dinosaur Brains
Before your next presentation or memo, use the recommendations in this chapter. Gauge performance afterwards if appropriate
Topic 17: Shell Games
Look into automations for simple tasks for GUIs
Topic 18: Power Editing
Turn off autorepeat and figure out how to delete entire words, sentences, blocks, etc.
Trying going a week without using the touchpad
Look for integrationsor try to find your own
Topic 19: Version Control Systems
Put all of your configuration files for your shell and other things into version control. You want to effectively replicate your computer if it were broken
Topic 21: Text Manipulation
Create script to convert a directory full of YAML files into JSON files
Convert files from camelCase to snake_case and v.v. Can you make this automatic and keep a backup?
Topic 31: Inheritance Tax
Next time you start subclassing, think if you can use an alternative with interfaces, delegation and mixins
Tips: 57, 58
Shared state doesn’t just include data, it also includes files, DBs, external services, etc. If anything is unexpected, then you will get really annoying errors
Concurrency bugs are quite hard to debug! Beware as it will be mindboggling (eg. one thread will complain file is missing but it actually is there)
If multiple resources requested at the same time, abstract as one resource and do the same semaphore strategy
Best practice: centralize semaphore locking and unlocking via API access so clients don’t have to worry
Sometimes, if errors are thrown while the semaphore is held by a process, then the semaphore is never released and every process hangs. You can avoid this by ensuring unlocks even after error raising
This works as long as everyone is following the semaphore convention. Otherwise, it’s chaos
In code, process of acquiring and releasing semaphore is lock/unlock, claim/release