Book cover

Pragmatic programmer

  • Inquisitive - asks questions
  • Critical thinker - checks the facts
  • Realistic - tries to understand the underlying nature
  • Jack of all trades - is familiar with a broad range of technologies
A broken window

1 broken window left unrepaired instills a sense of abandonment leading to more broken windows etc. Don’t leave broken windows (bad design, wrong decisions, poor code..) unrepaired. Fix each one as soon as discovered.

Be a continuous learner:
  1. Learn at least one new language every year
  2. Read a technical book each month
  3. Read nontechnical books too
  4. Take classes/courses
  5. Participate in local user groups and meetups

A thing is well-designed if it adapts to the people who use it.

ETC principle: Easier to change.

ETC:

  • decoupling
  • single responsibility Principe
  • good naming
DRY - Don’t repeat yourself

Key principle in writing maintainable code. DRY is not only about code duplications, but mainly about the duplication of knowledge. Test: when something has to change in code, do you find yourself changing multiple sections? That’s not DRY.

Not All Code Duplication Is Knowledge Duplication. Sometimes you need to check both age and quantity (both functions look the same), but they check different knowledge. The code is the same, but the knowledge they represent is different. That doesn’t violate DRY principle.

Sometimes when it comes to performance it’s okay to violate DRY. Depends.

Orthogonality

Concept of building systems that are easy to design, build, test and extend. Decoupling - changes in one do not affect others. Modular / component-based / layered systems.

Be careful when introducing 3rd-party libraries and toolkits.

Techniques to ensure orthogonality:

  • keep your code decoupled - modules
  • avoid global data
  • avoid similar functions

An orthogonally designed and implemented system is easier to test. Tests can be performed at the individual module level. Module (unit) testing is a lot easier than integration testing.

Orthogonality is very closely related to DRY principle. Minimal duplication + modules lead to more flexible, more understandable, and easier to debug programs.

Computer languages influence how you think about a problem.

Toolkit of Pragmatic Programmer

Tools you use are very important. Expect to add to your toolbox regularly. Always be on the lookout for better ways of doing things.

As Pragmatic Programmers, our base material isn’t wood or iron, it’s knowledge. The best format for storing knowledge persistently is plain text.

Debugging

Debugging is just problem-solving - attack it as such. Always try to discover the root cause of a problem, not just this particular appearance of it.

Debugging Strategies
  • Reproducing Bugs
    • The best way to start fixing a bug is to make it reproducible
    • The most important rule of debugging: Failing Test Before Fixing Code
    • Read the Damn Error Message
  • Sensitivity to Input Values
    • You can try looking at the place it crashes and work backwards
    • sometimes it’s easier to start with the data
    • binary chop the data until you isolate exactly which input values are leading to the crash
  • Logging and/or Tracing
    • Tracing statements are those little diagnostic messages you print to the screen or to a file that say things such as “got here” and “value of x = 2”
  • Rubber Ducking
    • A very simple but particularly useful technique for finding the cause of a problem is simply to explain it to someone else
  • Binary search - looking for a particular value in a sorted array
  • you can either check each item O(n) - not very effective, better to:
  • divide and conquer O(log n) - choose the value in the middle, found the item? Stop, else chop the array in half
  • value found > item? Item must be in the first half, otherwise second half
  • repeat

Applying Binary chop to debugging:

  • you’re trying to find out exactly which function mangled the value in error, you do a chop by choosing a stack frame somewhere in the middle and seeing if the error is manifest there
  • same with datasets - split the dataset into two, and see if the problem occurs if you feed one or the other through the app. Keep dividing the data until you get a minimum set of values that exhibit the problem

Always Use Version Control

Always. Even if you are a single-person team on a one-week project. Make sure that everything is under version control.

This I find cool:

“Many teams have their VCS configured so that a push to a particular branch will automatically build the system, run the tests, and if successful deploy the new code into production.”

Design by Contract

  • Code will never be perfect
  • Pragmatic programmer doesn’t trust himself either - that’s why he designs with contract
What is a correct program?

One that does no more and no less than it claims to do. Documenting and verifying that claim is the heart of Design by Contract.

Always Design with Contracts.

Any programming language, whether it’s functional, OOP, or procedural - DBC forces you to think.

Don’t ignore any errors. Pragmatic Programmers tell themselves that if there is an error, something very, very bad has happened.

Crash early - one of the benefits of detecting problems as soon as you can is that you can crash earlier, and crashing is often the best thing you can do

Use Assertions to Prevent the Impossible - thinking “but of course, that could never happen”? Add code to check it

Don’t use assertions in place of real error handling. Assertions check for things that should never happen.

Resource Allocation

  • Finish what you start - a routine that allocates a resource should also free it
  • Always take small steps - deliberate steps, checking for feedback and adjusting before proceeding

Decoupled code is easier to change

  • Inheritance in OOP must be designed and planned very well, otherwise, it can cause a lot of problems
Symptoms of Coupling
  • dependencies between unrelated modules/libraries
  • a simple change in one module that propagates through unrelated modules
  • fear of changing something as it’s unclear what might be affected
  • meetings that everyone has to attend because no one is sure who will be affected by a change
Don’t chain method calls
  • try not to have more than 1 “.” when accessing something
  • exception - things is really unlikely to change
  • in practice - everything should be considered likely to change
Avoid global data
  • global data includes external resources
  • database, datastore, file system, API… wrap these resources behind code that you control
  • If It’s Important Enough to Be Global, Wrap It in an API

Programming Is About Code, But Programs Are About Data

  • top-down approach: start with the requirement and determine its inputs/outputs
  • how to add conditional logic required for error checking?
  • basic convention: never pass raw values between transformations, wrap them in a data structure
Think of code as a series of (nested) transformations
  • overtime, this leads to writing cleaner code with shorter functions and flatter design

Alternatives to Inheritance

  • Interfaces and protocols
  • Delegation
  • Mixins and traits

Concurrency/Parallelism

  • Concurrency - execution of 2+ pieces of code act as if they run at the same time
  • Parallelism - they do run at the same time
  • Temporal coupling is about time - time is often ignored aspect of software architectures
  • We only think about a time when it comes to schedule - time left until we ship
  • Different aspect: role of time as a design element in software itself
  • Two aspects of this kind of time: concurrency/parallelism

Designing without time aspect

  • things tend to be linear
  • do this, then always do that
  • temporal coupling - method A must always come before method B
  • this approach is not very flexible and not very realistic
  • instead, allow for concurrency and think about decoupling any time or order dependencies
  • models & analyze the application workflows as part of the design
  • find out what can happen at the same time, and what must happen in a strict order
  • one way to do this is to use an activity diagram

Activity diagram

  • Example - writing the software for a robotic piña colada maker
  • activity diagrams show the potential areas of concurrency, but have nothing to say about whether these areas are worth exploiting
  • that’s where the design part comes in
  • when we look at the activities, we realize that number 8, liquify, will take a minute. During that time, our bartender can get the glasses and umbrellas (activities 10 and 11) and probably still have time to serve another customer

Things to look for when designing for Concurrency

  • activities, that take time, but not time in our code
  • querying database, accessing external service, waiting for user input - normally these would stall the program until they complete - opportunities to do something more productive in the meantime
Opportunities for Parallelism
  • Remember the distinction: concurrency is a software mechanism, and parallelism is a hardware concern. If we have multiple processors, either locally or remotely, then if we can split work out among them, we can reduce the overall time things take
  • The ideal thing is to split this way are pieces of work that are relatively independent—where each can proceed without waiting for anything from the others. A common pattern is to take a large piece of work, split it into independent chunks, process each in parallel, then combine the results

Listen to your inner Lizard

  • instincts/nonconscious lizard brain
  • stop what you are doing
  • give your brain some time
  • do something else for a while
  • take a walk, have lunch Still not found solution? Problem is too challenging?
  • externalize the issue
  • make doodles about the code, explain it to a coworker If still stuck, it’s time to…
Prototyping
  • Write I’m prototyping on a sticky note and stick it on the side of your screen
  • Prototypes are meant to fail
  • Just start writing some code. If you start having doubts, have bad feelings.. look at the sticky note

Don’t program by coincidence

  • always be aware of what you are doing
  • can you explain the code to a junior dev?
  • Understand it, letting it work without knowing why it works is a bad practice

Refactoring

  • as a program evolves, it will become necessary to rethink earlier decisions - this is perfectly natural
  • code needs to evolve

Refactoring - disciplined technique for restructing an existing body of code, altering its internal structure without changing its external behaviour

When should you refactor? When you have learned something, understand something better..

Test Driven Development

  • testing is not about finding bugs - major benefits of testing happen when you think about and write the tests
  • test is the first user of your code
  • writing tests makes you think about the code
  • be careful though not to become slave to TDD - writing very large amount of small, ineffective tests

Bottom-Up, Top-Down, End-to-End

  • top-down: start with the overall problem and break it down into smaller pieces
  • bottom-up: start at the smallest of problems and add new layers
  • problem with both approaches: we don’t know what we’re doing when we start
  • Instead, use End-to-End approach:
    • incremental approach
    • small pieces of end-to-end functionality
    • involve customer at each step

The Essence of Agility

  • agile is an adjective - it’s how you do something
  • there can never be an agile process
  • agility is all about responding to change, responding to the unknowns
  • So what to do? No one can tell you. Our recipe for working in an agile way:
    • Work out where you are.
    • Make the smallest meaningful step towards where you want to be.
    • Evaluate where you end up, and fix anything you broke.

Pragmatic teams

  • team - small, mostly stable entity of its own
  • 50 people are not a team
  • pragmatic team - 10-12 members, members come and go rarely, they know and trust each other