The Pragmatic Programmer

A fantastic book from Andrew Hunt and David Thomas, there is now a 20th Anniversary Edition.

Simply put, this book tells you how to program in a way that you can follow.

Additionally:

... they (pragmatic programmers) paid attention to what they were doing while they were doing it - and then they tried even better

Contents

What Makes a Pragmatic Programmer

  • Early adopter/fast adapter

  • Inquisitive

  • Critical thinker

    • Don't accept "because this is the way it's always been done"

    • Don't just stop at improving the problem itself, improve your own thinking too

    • Analyze what you read, hear and think!

  • Realistic

    • Try to understand the nature of each problem

    • Realism provides you a good feel of how difficult things are and can influence your estimations etc.

  • Jack of all trades

    • You can specialize but don't let that hold you back from adaptability

A Pragmatic Philosophy

  • Take responsibility, don't make excuses - provide options/alternatives

  • Software entropy - "rot" or neglect. Entropy is "disorder".

  • Catalyst for change

    • Don't ask permission (since you'll be rejected), instead, work on incremental changes, show, suggest etc.

    • Soup of Boiled Stones

      • Analogy: bunch of soldiers in war-zone with no food start boiling stones in a pot that slowly attracts the villagers

  • Good enough software

Your Portfolio

Build one

  • Invest regularly

  • Diversity in tools, knowledge

  • Manage risk

  • By low, sell high - early adopter

  • Review and re-balance - languages, technology changes

Goals

Some interesting ideas to keep in mind:

  • Learn at least one new language a year

  • Read one technical book a quarter

  • Read non-technical books too (I prefer this one)

  • Take classes

  • Participate in local user groups, create a network to glean information about other companies etc.

  • Experiment in different environments e.g. Windows/Linux

  • Stay current - magazines etc.

Communication

  • Know what you want to say

    • Take notes before meetings

  • Know your audience, are they end-users, technical...

  • Choose your time - Friday 5 PM?

  • Make it look good - Latex etc.

  • Listen to people

  • Always get back to people - even if you can't immediately, "I'll get back to you.." goes a long way

Reflect on:

  • What do you want them to learn?

  • What is their interest in what you've got to say?

  • How sophisticated are they?

  • How much detail do they want?

  • Whom do you want to own the information?

  • How can you motivate them to listen to you?

A Pragmatic Approach

Don't Repeat Yourself

This applies also to documentation and other non-code related matters. Changing things in more than one place is duplicate effort - a recipe for stale information.

How it occurs

  • Imposed duplication

    • Developers feel they have no choice - the environment seems to require duplication

    • Multiple representations of information - use code generators as much as possible e.g. DB schema

    • Documentation and code - linking user specifications to tests can automate what tests fail/need to be fixed

  • Inadvertent duplication

    • Unaware

    • Design issues

  • Impatient duplication

    • Lazy

  • Interdeveloper duplication

    • Multiple people or different teams duplicate

    • Solution: make it easy to reuse and communicate

Inadvertent duplication example

// length is defined by start and end
// duplication
class Line {
public:
    Point start;
    Point end;
    double length;
};

// better
class Line {
public:
    Point start;
    Point end;
    double length() { return start.distanceTo(end); }
};

Orthogonality

A kind of independence/decoupling. Two or more things are orthogonal if changes in one don't affect any others. For example, a change in the DB layer will not impact the UI. Visually, imagine a straight line on a X-Y axis.

AOP: Aspect Oriented Programming.

Productivity

  • Changes are localized

    • Testing and debugging is much easier

  • Small changes > Big changes

  • Loosely coupled systems are easier to reconfigure and re-engineer

Reduce Risk

  • "Sick" components are small and contained

  • Less fragile, bugs are contained to that component

  • Will not be tightly coupled to a technology, vendor etc. since it will only be so for that single/few components

Reversibility

  • Never commit, always be soft and pliable - leave your self exits

  • Design decoupled, well defined interfaces to be able to "change horses mid-stream"

    • This is does not mean to change interfaces but allow the implementations to be easily changed

  • Always automate/generate metadata/code for "variables" e.g. requirements, marketing

Tracer Bullets

  • Write code that "glows in the dark"

    • Ready, fire, aim repeat

    • Starting in the dark (greenfield) or an opaque system

      • Write some code, run test

      • Write, modify, run test

      • Repeat until you are closer to your target but the idea is to keep modifying your path

  • Useful when you need to produce results quickly

    • Use this to get feedback early and make adjustments accordingly

  • Progress is visible and is results driven

  • You don't always have to hit the target! Small code has low inertia

Prototyping vs Tracer Bullets

  • Prototyping is strictly for POC and the code is *throwaway"

    • Do not make the mistake of using as the future code base

  • Tracer code is the skeleton for the body

    • Lean and but complete

Estimating

  • It saves you time

  • How accurate is enough?

Duration
Quote estimate in

1-15 days

days

2-8 weeks

weeks

8-30 weeks

months

30+ weeks

think hard before giving an estimate

  • Scope out before answering estimates e.g. define any assumptions, ask others

  • Building a model can elucidate any shortcomings or overlooked question/variables

    • It may also show what can be compromised (design/features) due to time

Building the Model

  • Break the model into components

  • Give each parameter a value

  • Estimates are based on sub-estimates and so on

    • Your largest errors will come from this

    • Always revisit this and identify why

  • Every sprint, estimates can be revisited

    1. Check requirements

    2. Analyze risk

    3. Design, implement, integrate

    4. Validate with users

"I'll get back to you"

If you are not sure or confident about your estimates, that phrase will always be better than giving a very inaccurate estimate. It also may imply that you take estimates seriously.

Pragmatic Paranoia

Analogy:

  • You're the best driver on Earth, everyone else sucks

  • We drive defensively, look out for trouble before it occurs (proactive), anticipate danger and never put ourselves into situations where we cannot extricate ourselves from - reversibility

  • We don't trust ourselves either, you never write perfect code

Design by Contract

Design by Contract (DBC) is a technique that focuses on documenting (and agreeing upon) the rights and responsibilities of software modules to ensure program correctness. A correct program is one that does no more or less than it claims to do.

DBC forces you to define requirements and guarantees upfront. By enumerating the input domain range, boundary conditions, what the routine promises to deliver at design time is a huge improvement.

Every function and method has:

  • Preconditions

    • What must be true in order for the routine to be called - the routine's requirements

    • A routine should never get called when its preconditions would be violated - it is the caller's responsibility to pass good data

  • Postconditions

    • What the routine is guaranteed to do - the state of the world when the routine is done

    • The fact that the routine has a precondition, it implies that it will conclude - infinite loops aren't allowed

  • Class invariants

    • A class ensures that this condition is always true from the perspective of a caller

    • During internal processing of a routine, the invariant may not hold, but by the time the routine exits and control returns to the caller, the invariant must be true

Example

/**
 * @invariant for all Node n in elements()
 *      n.prev() != null
 *          implies
 *              n.value().compareTo(n.prev().value()) > 0
 */
public class dbc_list {
    /**
     * @pre contains(aNode) == false
     * @post contains(aNode) == true
     */
    public void insertNode(final Node aNode) {
        ...
    }
}

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

Shy code

For orthogonality, do not reveal information about itself unless really necessary. This avoids the caller and other components knowing unnecessary details and making assumptions etc.

Lazy code

For DBC, "lazy code" is instead promoted where you are strict in what to expect but promise as little as possible in return.

Semantic Invariants

  • An inviolate requirement - a "philosophical" contract

  • Example: debit card transaction switch

    • User of a debit card should never have the same transaction applied to their account twice

    • No matter what error (e.g. catastrophic), it should always side on not processing the transaction

    • Err in the favor of the consumer

  • These should be simple law that is directly driven from the requirements - they aren't requirements themselves but are central to the very meaning of a thing

  • It can be applied to complex problems, drive and re-align design and implementation etc.

Dead Programs Tell No Lies

Crash early! Don't trash!

Bend, or Break

Life doesn't stand still

So doesn't code.

Decoupling and the Law of Demeter

  • Strong cells, limit interactions make "good principle"

  • Like a prison, with shy code, the less that is known about other cells/inmates the better

    • Cells can be replaced easily

    • Problems are contained

Minimize coupling

Don't couple modules if possible. Declare them explicitly e.g. in the constructor.

Law of Demeter for functions

class Demeter {
private:
    A *a;                       // The Law of Demeter for functions states
    int func();                 // that any method of an object should call
public:                         // only methods belonging to:
    void example(B& b);
}

void Demeter::example(B& b) {
    C c;
    int f = func();             // itself

    b.invert();                 // any parameters that were passed in to the
                                // method
    a = new A();
    a->setActive();             // any objects it created

    c.print();                  // any directly held component objects
}
  • Consider trade-offs, delegation/wrappers at the cost of performance

    • If it is important to compromise, then make sure it is known and controlled

  • Large Scale C++ Software Design

Metaprogramming

Metaprogramming is to remove the details from the code - metadata. Design applications that are highly configurable - "soft".

Leave details in the metadata/configuration so if new requirements of rules change, your application will not break.

  • Decouples your design

  • You could implement several different projects using the same application engine using different metadata

  • Can be used as a workaround for bugs

  • Complex logic (business) can be captured in metadata easier

    • Higher level languages are closer to requirements

    • PM/users can read/provide feedback

    • Bugs are more to do with code, not requirements

    • Code generators can be an option

Temporal Coupling

  • Always design for concurrency

    • Going the other way is much harder

  • Coupling in time when design revolves around sequential order

  • Gain flexibility and reduce any time-based dependencies in areas such as workflow analysis , architecture, design and deployment

Workflow Analysis

  • Used to model and analyze the user's workflow as part of requirements analysis

  • "What can happen at the same time, and what must happen in strict order" - dependencies

Example: making a pina colada

By observing the workflow, optimizations were made to parallelize the different tasks instead of performing this sequentially. Not only does this benefit performance but the temporal coupling become apparent.

 +--------------------+  +-------------------------+  +--------------------+
 |  8. Open Mix       |  |  1. Open blender        |  |  4. Measure rum    |
 +---------+----------+  +------+-----+-----+------+  +-----------+--------+
           |                    |     |     |                     |
           |                    |     |     |                     |
           v          v---------+     |     +---------v           v
+--------------------------+          |          +--------------------------+
           |                          |                           |
           v                          v                           v
 +---------+----------+  +------------+------------+  +-----------+--------+
 |  3. Put mix in     |  |  8. Add two cups of ice |  |  5. Pour in rum    |
 +--------------------+  +-------------------------+  +-----------+--------+
                                                                  |
                                                                  |
                                                                  v
                                                      +-----------+--------+
                                                      |  7. Close blender  |
                         +-------------------------+  +-----------+--------+
                         |  11. Get pink umbrellas |              |
 +--------------------+  +------------+------------+  +-----------+--------+
 |  10. Get glasses   |               |               |  8. Liquefy        |
 +--------------------+               |               +-----------+--------+
                                      |                           |
                                      |                           v
                                      |               +-----------+--------+
                                      |               |  9. Open blender   |
                                      |               |                    |
                                      |               +--------------------+
                                      v
+---------------------------------------------------------------------------+
                                      |
                                      v
                         +------------+------------+
                         |  12. Serve              |
                         +-------------------------+

While You are Coding

Refactoring

Software development often uses the building construction analogy:

  1. Architect draws up blueprints

  2. Contractors dig the foundations, build the superstructure, wire and plumb, and apply finishing touches

  3. The tenants move in and live happily ever after, calling building maintenance to fix any problems

Unfortunately software doesn't work that way. Instead of construction, it's more like gardening.

Gardening analogy

  • Software is more organic than concrete

  • You may plan many things in a garden according to an initial plan and conditions

  • Some thrive, others end up as compost

  • You may move plantings relative to each other to take advantage of light, shadow, wind and rain

  • Overgrown plants get split/pruned, colors may clash and be moved for more aesthetically pleasing locations

  • You pull weeds, and fertilize plantings that are in need of extra help

  • You constantly monitor the health of your garden, making adjustments (weed, soil, layout etc.) as needed

Business people are more comfortable with the building construction metaphor since it is more scientific than gardening, it's repeatable, there's a rigid reporting hierarchy for management etc. But we're not building skyscrapers - we aren't as constrained by the boundaries of physics and the real world.

Gardening is much closer to the realities of software development.

Don't live with broken windows

Refactor Early, Refactor Often

Code that's easy to test

  • Log formats must be consistent

    • Log parses

  • Have your app include a webserver for debugging

    • Shows logs, internal statuses etc.

    • Useful that is easy to integrate

Before the Project

The Requirements Pit

Don't Gather Requirements - Dig for them

Digging for Requirements

  • They are not objects to be simply "gathered", work must be done to analyze them

  • Need to differentiate between policies (what is true today but viable to change) and requirements

  • Requirements are not often clear, it is difficult to define good ones

  • Discover the reason why users do a particular thing - rather than just the way they currently do it

    • Understand the user, don't just observe

    • You're here to solve a business problem, not to just meet requirements

    • Become a user, literally, perform their role/shadow and you'll get feedback, insights, trust, empathy etc.

    • Work with a user to think like a user

    • Managerial view will not do justice to end users - key information is often filtered out

Documenting Requirements

  • Long story short: use a website for documentation

    • It has the largest audience scope for people to read, contribute, feedback etc.

    • But at the end of the day, use what resonates best with your audience

  • Use templates for requirements e.g. Cockburn's use case templates (p207, Fig 7.2)

  • Don't over-specify or detail, they are not design

Seeing further

  • The Y2K problem was flawed from a business practice/automation to engineering

  • If people saw the need to abstract dates since systems will consume such data (requirement)

    • This would have exposed/hinted the need for maths/design

Book-keeping

  • Track changes in requirements, scope etc.

  • Glossary for terms used with Engineering/Management

Solving Impossible Puzzles

Don't Think Outside the Box - no, Find the Box

Degrees of freedom

  • Identify constraints, recognize the degrees of freedom you have

  • Don't dismiss something unless you can prove it should be

There Must Be an Easier Way

Ask the following questions before yelling and screaming:

  • Is there an easier way?

  • Are you trying to solve the right problem, or have you been distracted by a peripheral technicality?

  • Why is this thing a problem?

  • What is it that's making it so hard to solve?

  • Does it have to be done this way?

  • Does it have to be done at all?

Not Until You're Ready

Listen to Nagging Doubts - Start When You're Ready

  • If you have doubts, then there's probably a reason for that - understand what it is

  • The idea of starting at the right time

  • Starting on a blank canvas

    • Procrastination

    • Prototype/POC

      • This could determine if you were procrastinating and/or lead to real development and design revelations

The Specification Trap

  • When you specify all the details to remove all the ambiguity

    • Thats a TRAP!

  • It is impossible to cover every nuances of a requirement/system

    • Include human interpretations, restrictions in the domain language, teams, human factors etc.

  • Example: describe how to tie a shoe lace

    • Some things are easier done than said

  • A pragmatic programmer will look at requirements digging, design and implementation as a single process

  • Over-specification have diminishing returns

    • Harder to move on and hack code - if this is your team, consider prototyping/tracer bullets

Circles and Arrows

Don't Be a Slave to Formal Methods

  • Formal methods: CASE, waterfall development, ER diagrams etc.

  • These are fads; they enjoy a period of popularity adrift in a sea of others and developers hang on these pieces like driftwood

    • Learn to move on, adapt

  • Prototyping and showing progress to end-users is often much better than formal methods e.g. ER diagrams

  • Formal methods encourage specialization, silos between teams and a "us vs them" mentality, barriers, communication inefficiencies

    • Better to merge systems together and encourage a seamless process

    • Can lead to toxic culture

  • Use formal methods but think carefully before embracing, we should pick the best, constantly improve and always tailor for what works for your team

Pragmatic Projects

  • We must relinquish individual philosophy to talk about larger, project-sized challenges

  • Pragmatic teams amplify pragmatic programmers

Pragmatic teams

No Broken Windows

  • Quality is a team issue

  • If the team discourages the fixing of small problems - red flags

  • Do not ignore

Boiled Frogs

  • Analogy: frogs didn't notice themselves slowly being boiled

  • Don't be in the code, be part of and view it above to notice changes and gain control

  • Appoint someone who observes scope changes, requirements, time scales etc.

  • Be aware of changes in the team

Communicate

As some have said before: "there is no such thing as over-communication".

  • Meetings should have structure, well-prepared and should be looked forward to

  • Documentation should be crisp, accurate and consistent

  • Team name - identity to build upon and for the team to attach to across projects

DRY

  • Don't duplicate effort, appoint someone for coordination like a librarian

  • Appoint people in the team e.g. API/DB person

Orthogonality (team)

  • Avoid silos, isolation - it doesn't work

  • Split teams based on features/functionality not by component

  • Large cohesive teams are like code - they are organized like so

  • Use DBC - teams promise behavior and if that changes, it must be communicated

    • For example, changes in telemetry in Engineering will impact Legal/Marketing - coordination is key

  • Projects should have 2 heads/leaders

    1. Technical

      • Developer philosophy, code style, assigns responsibilities to teams, arbiter in discussions

    2. Administrative

      • Business requirements, management, progress, resources, priorities, coordination

Automation

  • Appoint tool builders, makefiles, scripters

  • Auto-generated documentation is powerful and encourages the DRY principle

    • For example, you can use Jekyll to place code and documentation side by side e.g. /docs, /src, a change in code requires changes in the documentation

    • Proprietary software for documentation e.g. Confluence, comes at a cost and this is really bad

Know When to Stop Adding the Paint

  • Let each individual show their individuality, give them structure and space to shine

  • Know when to stop adding the paint - it can be stifling

Ruthless Testing

Unit tests

  • Consider TDD approaches

  • They will not capture all bugs

Integration tests

  • Tests integration of your system with other (sub)systems

  • Fertile breeding ground for bugs (like a garden!)

Validation and Verification

  • Does it address what the user needs - it may be what they wanted, but is it needed?

  • Does it meet the functional requirements of the system? Test it

Performance testing

  • Ensure you run stress and scale tests

Usability testing

  • Real users and environment conditions

  • Look at usability in terms of human factors

  • Are there any misunderstandings of the requirements

    • Tools that fit engineers should also fit users!

  • Failures in usability are as bad as P0 bugs

How to Test

  • Regression testing

    • Important you check for regressions in new features

  • Test data

    • How to generate useful test data?

    • Some need to be manual since data must be from the real-world

  • Testing the tests

    • Introduce bugs on purpose - like AWS canaries!

    • Check that tests fail (you can appoint someone)

  • Test state coverage, not code coverage

    • Even small programs can have many states

Great Expectations

  • Success of a project is measured how well it meets the expectations of its users

    • It if falls below the expectations, it doesn't matter how good the design is, it is a FAILURE

    • Gently exceed your user's expectations

  • Communicate expectations

    • User's have a "vision" which can be emotional/misinterpreted

    • Liaise with them frequently so small changes in expectations can be managed - there's a fine line here

      • It provides feedback early and opportunity to show progress

Extra Mile

  • Don't surprise them, delight them

  • Small QOL additions like tooltips, hotkeys, quick references, log file analyzers, splash screen (branding)

    • Engineering efforts are small but has large returns

Pride and Prejudice

Sign Your Work

  • Like a craftsmen, you should be proud of your work

    • Don't let this deter you from potential prejudices towards you (defensive)

  • People should see your name and expect it to be solid, well-written, tested and documented

    • A professional job written by a real professional

    • A Pragmatic Programmer

Last updated