A review: The Pragmatic Programmer

Hello, this is an attempt to condense this classic book to about.. some very less words, hoping to still capture the core of each topic.

The article should be kept as a reference to the important pointers. However, make a point of reading this book: seriously. :crossed_fingers:

Resource: https://bit.ly/2ARPd7e


Chapter 1: A Pragmatic Philosophy

We who cut mere stones must always be envisioning cathedrals!

It’s a continuous process: coding.

The greatest of all weaknesses is the fear of appearing weak.

Take responsibility. Provide solutions not excuses.


Software Entropy

  • Broken window theory- Don’t leave broken code unrepaired/ not acted on.

  • Working around startup fatigue:

    • Work out what you can reasonably ask for.
    • Develop it, let people see it, pretend it’s not important.. this attracts people to it. Classic stone soup vs frog soup.(Ask me for the stories :0) )

Remember the big picture.


Good enough software

  • You can discipline yourself to write software that’s just good enough. Capturing user requirements is good enough.

Know when to stop.


Invest in Knowledge

It always pays pays interest: knowledge.

Knowledge and experience are you most professional assets.

  • Serious investors invest seriously.
  • Diversification is the key to long term success.
  • Smart investors balance between conservative and high risk.
  • Investors try to buy low and sell high for maximum return.
  • Portfolios should be reviewed and rebalanced periodically.
  • Pragramatic programmer goals
    • Learn at least one new language every year. It broadens your thinking.
    • Read a technical book every quarter.
    • Read non technical books too.
    • Take courses.
    • Experiment on different environments.
    • Stay current.
    • Get wired.

Communicate

“I believe it is better to be looked over than it is to be overlooked.”

  • Hacks for the nerdy programmer

    • Know what you want to say.
    • Know your audience- Determines how technical you really need to get, if at all.
    • Choose the right moment to communicate.
    • Choose your style- Consistent styling for docs, etc is best.
    • Make it look good- Keep the attention; show your personality.
    • Involve your audience.
    • Listen.
    • Get back to people when it requires.

Chapter 2: A Pragmatic Approach

Don’t Repeat Yourself!

Don’t duplicate knowledge throughout your system.

  • Programmers are constantly in maintenance mode.(This is a routine part of the whole development process).
  • Localise the impact of duplication.
  • Have well understood division of responsibility; strong technical lead and a clear design.

Make it easier to reuse code than to write it yourself.


Orthogonality

Decouple.

  • Eliminate the effects between unrelated things.
  • Changes become localised.
  • Reuse is promoted.
  • Reduces risk of failure.
  • Better testing.

Separate the infrastructure from the application.

The larger the people needed when discussing changes, the less orthogonal the team is.

  • Layered approach- A powerful way to design orthogonal systems. Each layer uses only the abstractions provided by the layers below.
  • Don’t rely on properties you can’t control. Choose your technologies wisely: libraries, toolkits etc.
  • If an object persistence scheme is transparent, then it is orthogonal.
  • To maintain orthogonality in code..
    • Keep code decoupled. Write shy code! - If you need to change an object’s state, get the object to change it for you.
    • Avoid global data. Explicitly pass any required context to your modules.
    • Avoid similar functions.

Reversibility

Be constantly critical of your code.

Refractor!

  • Unit testing is considerably easier than integration tests.
  • Tag bug fixes. Analyse files affected.

There’s nothing more dangerous than an idea if it’s the only one you have.

Critical decisions are not easily reversible; however, there are no final decisions.

Have a flexible architecture. Whatever mechanism you use, make it reversible.


Tracer bullets

Because projects take time, you can pretty much guarantee environment to change before you are done.

  • Use tracer bullets to find target. This is an incremental approach to development.
    • Users get to see the work early.
    • Developers build a structure to work in.
    • You get to have an integration platform.
    • You have something to demo.

A tracer bullet is something that gets us from a requirement to some aspect of the final system quickly, visibly, and repeatably. It shows what you are hitting(user requirements), you then adjust your aim until you hit the target. This is the point.

A prototype is something that gets you intelligence gathering and trying out ideas. It is disposable.

  • Prototypes are designed to test specific aspects of a system.

  • Analyses and exposes risk.

  • Offers the chance for correction at a reduced cost. They don’t have to be code-based.

  • What to look for in an architectural prototype..

    • Are responsibilities of major components well defined?
    • Are collaborations between major components well defined?
    • Is coupling minimised?
    • Can you identify potential sources of duplication?
    • Are interface definitions and constraints acceptable?
    • Can every module access the data it needs?
    • Can it have access to the data when it needs it?

    Domain languages

    The limits of a language are the limits of one’s world.

    Computer languages influence how you think about a problem and how you think about communicating.

    The language of the problem domain also suggests a programming solution.

    Program close to the Problem domain.

    Language you implement can be used in these two different ways:

    Data languages- Produce some data structure used by an application. Often used to represent configuration info.

    Imperative languages- Language is actually executed that contains statements, control constructs etc. This is mostly a maintenance programmer’s domain

    Tradeoff in extendability and maintenance.

    Easy development or easy maintenance?


    Estimating

    In the process of producing an estimate, you come to understand more about the world your program inhabits.

    Estimate! Avoid surprises.

    • The units used make a difference in the interpretation of results.
    • Ask someone who has been in a similar situation.
    • Have a grasp of the domain scope.
    • Build a model. Break it into components. Each component has parameters- give these values. Calculate the answer.

    Iterate the schedule with the code.


    Chapter 3: The Basic Tools

    Tools amplify talent.

    The better your tools and the the better you know to use them, the more productive you become.

    Always look out for better ways of doing things.


    The power of plaintext

    • The problem with binary formats is that the context to understand the data is separate from the data itself.
    • Keep knowledge in plaintext- It is acceptable to have metadata in plaintext.
    • Con: More space than binary formats.
    • Con: Computationally more expensive to interpret and process.

    Shell games

    You can combine your tools via terminal to create macro tools.

    Use the power of command shells!


    • Know a single editor very well and use it for all your tasks.

    Always use source code control.

    Those who cannot remember the past are condemned to repeat it!

    • Do ruthless testing.

    It is a painful thing, to look at your own trouble and know that you yourself and no one else, has made it.


    Bug reproduction

    • Don’t panic.
    • Get the user’s bug report or use artificial tests to.. reproduce the bug.
    • Use a debugger.
    • Seeing a stack trace can tell you directly how you got the bug. Follow the stack trace.
    • Rubber ducking. :0)
    • A debugging checklist..
      • Is the bug reported as an underlying bug, or as a symptom?
      • Is the bug really in the compiler? OS? or your code?
      • How would you explain the bug?
      • Are the unit tests complete?
      • Do these conditions seem to exist anywhere else?

    Learn a text manipulation language. Python, sed, awk.

    Write code that writes code:

    • Active code generators.
    • Used each time their results are needed.
    • With these, you can take a single representation of knowledge and convert it into all other forms your application needs.
    • Passive code generators.
    • Run once to produce a result.
    • They save typing.
    • Basically parameterized templates.

    Chapter 4: Pragmatic Paranoia

    You can’t write PERFECT software.

    Code in defense against mistakes, even your own.

    • Design by contract.
    • Assertive programming.
    • Dead programs, tell no lies.

    Design by contract

    • Every function/method in a software system does something.
    • The routine may have expectations:
      • Preconditions- Responsibility is to the caller to pass good data.
      • Class invariants- Ensure conditions within the routine always true from the caller’s perspective.
      • Using final, @pre keywords etc.

    If all routine’s preconditions are met by the caller, the routine has to guarantee all post-conditions and invariants will be true when it completes.

    If either party(caller and routine) fails, then a remedy is invoked:

    • An exception.
    • A termination.

    Write lazy code.

    • Subclasses must be usable through the base class interface without need for the user knowing the difference.
    • Who is responsible for checking precondition? Caller or routine? The routine, but before it is entered. Any explicit checks must be done by the caller.

    By expressing the domain of a function in the precondition of the routine, you shift the burden of correctness to the call.


    Dead programs tell no lies

    Crash early! Don’t trash.*

    There is luxury in self-reproach. When we blame ourselves we feel no one else has a right to blame us.

    • If it can’t happen, use assertions to ensure that it won’t.
    • Don’t use assertions in place of real error handling.

    Balancing and exceptions

    • Exceptions should be reserved for unexpected events.

    An error handler is a routine that is called when an error is detected.

    • Balancing resources:

      • Finish what you start. The routine allocating a resource should be responsible for deallocating it.

      • Nest allocations..

        These are routines needing more than one resource at a time.

        • Deallocate in LIFO.
        • When allocating same set of resources in different places, allocate in same order. This reduces deadlock.

      It is always a good idea to build code that actually checks that resources are indeed freed appropriately.

      Check for memory leaks too.


Chapter 5: Bend Or Break

We need to make effort to write code that’s as flexible as possible.

Good fences make good neighbours.


Decoupling

  • Organise your code into modules and limit interaction between them.

Minimise coupling.

  • Systems with unnecessary dependencies are hard and expensive to maintain.

  • The law of Demeter

    Minimise coupling between modules in a program.

    The law of demeter for functions: Any method of an object should call only methods belonging to:

    • Itself.
    • Any parameters passed into the method.
    • Any objects it created.
    • Any directly held component objects.

    Cost: Your module must delegate and manage any/all subcontractors directly without involving clients.


Metaprogramming

No genius can overcome a preoccupation with detail.

  • Dynamic configuration

Configure, don’t integrate.

  • Use metadata to describe configuration options for an app.
  • Our goal is to think declaratively by specifying what is to be done, not how; and create dynamic and adaptable programs.
  • Put abstractions in code, and details in the metadata.
  • A flexible approach- write programs that reload configurations while they run.

Temporal coupling- About time: concurrency and ordering.

We need to allow for concurrency.

Analyse workflow to improve concurrency.

Events- Can be used to signal changes in one object that some other object may be interested in.

Don’t spam objects!!

Separate views from models.


Chapter 6: While You Are Coding

Coding is not mechanical. There are decisions to be made every minute.

  • As devs, we work in minefields. There are hundreds of traps waiting to be caught.

Program deliberately!

  • Accidents of implementations are things that happen simply because that’s the way code is currently written.
  • For routines you call, rely on documented behaviour. Don’t program by coincidence.
  • Coding deliberately involves..
    • Always be aware of what you’re doing.
    • Don’t code blindfolded.
    • Proceed from a plan, whatever it is.
    • Rely only on reliable things.
    • Document assumptions.
    • Don’t just test code, test assumptions also.
    • Don’t be slave to history.
    • Prioritise effort.

The O-Notation.

  • Estimate the order of your algorithms.
  • Test your estimates. Use code profilers.
  • Be pragmatic about choosing algorithms; the fastest is not always the best for the job.

Refractoring

Code needs to evolve, it is not static.

  • Things that qualify for refactoring..
    • Duplication.
    • Non-orthogonal design.
    • Outdated knowledge.
    • Performance.

Refactor early, refactor often.

  • Refactoring is redesign.
  • Don’t try to refactor and add functionality at the same time.
  • Make sure you have good tests before starting.

Write Code that’s Easy to Test.

  • Unit testing- This is code that exercises a module.
  • Testing against contracts: We want to test that a module delivers the functionality it promises over a range of test cases.
  • Test subcomponents first.
  • Design to test.
  • Test harnesses should include these capabilities..
    • A standard way for setup and cleanup.
    • A method to select individual tests.
    • A way to analyse output for results.
    • A standard form of failure reporting.

Use a test window:

  • Log files containing trace messages.
  • Diagnostic control window.

Test your software, or user users will!

Don’t use wizard code that you don’t UNDERSTAND.


Chapter 7: Before The Project

Don’t gather requirements, dig for them.

  • Policy may end up as metadata in the project.
  • Discover the underlying reasons why users do a particular thing, rather than the way they do it.
  • Become a user. Work with a user to think like a user.
  • The use case template..
    • Characteristic info.
    • Main success scenario.
    • Extensions.
    • Variations.
    • Related info.
    • Schedule.
    • Open issues.
  • Good requirements remain abstract.
  • Requirements are not architecture; they are not design. They are needs.

Abstractions live longer than details.

  • Don’t think outside the box, find the box..
    • Is there an easier way?
    • Are you trying to solve the right problem?
    • Why is this thing a problem?
    • What makes it hard to solve?
    • Does it have to be done this way?
    • Does it even have to be done?

He who hesitates is sometimes saved.

Listen to any nagging doubts- start when you are ready.

  • It is naive to assume a specification will ever capture every detail and nuance of a system.
  • Some things are better done than described.

Don’t be a slave to formal methods.


Chapter 8: Pragmatic Projects

Quality is a team issue.

  • Everyone in a team should actively monitor the environment for changes.
  • Great teams have a distinct personality.
  • Have a project librarian(s) dutied to organise workings/documentation about the (sub)projects.

Organise team around functionality, not job functions.

Civilisation advances by extending a number of important operations we can perform without thinking.

Don’t use manual procedures.


  • Test early. Test often. Test automatically.
  • Coding is not over until all tests.
  • What to test..
    • Regression testing.
    • Unit testing.
    • Integration tests.
    • Real vs synthetic test data.
    • Validation and verification.
    • Resource exhaustion.
    • Performance tests.
    • Usability tests.
    • Errors and recovery.
    • Testing tests.
    • Test thoroughly.
  • Use saboteurs to test your testing.
  • Test state coverage, not code coverage.

Find bugs once.


The palest ink is better than the best memory.

Build documentation in, don’t bolt it in.

  • Gently exceed your users’ expectations.

Sign Your Work!

Signed off,

A writer has no name. ;)

AHCTF2021: NameCheck

Snappy on Travis

comments powered by Disqus