Preface

  • Traits of a pragmatic programmer:
    • 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)
  • Online communication: everything above applies plus:
    • Review everything before you hit send
    • Check for any auto-correct mistakes
    • 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.
  • Beware of the following antipattern
try:
  thing = allocate()
  process(thing)
finally:
  deallocate(thing)
  • 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