I often contemplate what makes great developers stand out from good ones. After years of seeing teams repeatedly face the same problems, I keep returning to one book: The Pragmatic Programmer by Andrew Hunt and David Thomas.

Some developers know of this book, but few have internalized its principles. They still make the same mistakes Hunt and Thomas warned about decades ago.

I recommend reading A Pragmatic Programmer Book Review and then purchasing the book to support the authors.

You don’t need to read the entire book to improve your code today. This list offers essential principles as practical advice, but it’s no substitute for the impactful stories in the book.

Care About Your Craft

Why spend your life developing software unless you care about doing it well?

This isn’t about perfectionism but taking pride in your work. Caring about your craft drives you to improve, ask questions, experiment, and learn.

Many developers treat coding as a paycheck factory, writing functional code without considering maintainability, readability, or elegance. That’s not artisanship, but assembly line work.

Think About Your Work

Turn off autopilot, take control, and constantly critique your work.

When writing functions, ask if it’s the best solution. During debugging, identify the root cause instead of just fixing the symptom.

The standout developers aren’t always the smartest; they’re the ones who measure twice, then commit.

Provide Options, Don’t Make Excuses

Instead of excuses, offer options and explain how to salvage the situation.

I’ve lost track of how often I’ve heard “That’s not possible” when what the developer truly means is “I don’t know how to do that.” There’s a difference.

When you hit a wall, figure out what’s blocking you and suggest alternatives. Your team will respect you, and you will be able to solve more problems.

The Broken Windows Principle

A broken window left unrepaired fosters abandonment, which in turn leads to increased window breakage, littering, graffiti, and structural damage.

Don’t Live with Broken Windows

This principle can change how you handle codebases. Fix long-standing TODO comments, functions like doStuff(), or copied code immediately.

Not because it’s broken, but because it’s a broken window. Left alone, it signals to other developers that quality doesn’t matter here.

The DRY Principle: Don’t Repeat Yourself

Every knowledge piece must have a single, clear, authoritative form within a system.

This isn’t just about copying code but duplicating knowledge. Repeating business rules or scattered configurations violates the DRY principle.

The issue isn’t duplication but the risk of forgetting to update it in three places when changes are needed.

Orthogonality: Eliminate Effects Between Unrelated Things

Two or more things are orthogonal if changes in one don’t affect others.

Write “shy” code: code that doesn’t reach out to everything, does one thing well, and ignores the rest of the system.

Orthogonal code makes changes easier and more isolated. You can modify, test, and reuse components without affecting others.

Design by Contract

A correct program only does what it claims.

Use preconditions, postconditions, and invariants. Be strict about acceptances upfront and promise as little as possible.

This isn’t about writing more code. It’s about being explicit about your code’s expectations and guarantees. Being explicit makes bugs obvious; vagueness hides bugs.

Crash Early

A dead program does less damage than a crippled one.

When your code discovers that something that was supposed to be impossible just happened, your program is no longer viable. Don’t try to limp along. Crash immediately and crash loudly.

I’ve seen many systems that log an error and continue handling impossible situations. These systems are nightmares to debug because the error occurs elsewhere, but symptoms appear everywhere.

Refactor Early, Refactor Often

Code needs to evolve. It’s not a static thing.

When should you refactor?

  • You’ve discovered a violation of the DRY principle.
  • You’ve found code that could be more orthogonal.
  • Your knowledge of the problem has improved.
  • The requirements have evolved.
  • You need to improve performance.

Avoid refactoring and adding features at the same time. Ensure thorough tests are in place first. Take small, deliberate steps.

Test Early, Test Often, Test Automatically

Tests that run with every build are the most effective.

Finding a bug early reduces the costs of fixing it. “Code a little, test a little.”

The 70 Tips Quick Reference

Here are 70 tips from The Pragmatic Programmer, organized for quick reference:

Philosophy and Approach

1. Care About Your Craft

Why spend your life developing software unless you care about doing it well?

2. Think! About Your Work

Turn off the autopilot and take control.

3. Provide Options, Don’t Make Lame Excuses

Instead of excuses, provide options.

4. Don’t Live with Broken Windows

Fix bad designs, wrong decisions, and poor code when you see them.

5. Be a Catalyst for Change

Show people how the future might be and help them participate in creating it.

6. Remember the Big Picture

Don’t get so engrossed in the details that you forget to check what’s happening around you.

7. Make Quality a Requirements Issue

Involve your users in determining the project’s real quality requirements.

Knowledge and Learning

8. Invest Regularly in Your Knowledge Portfolio

Make learning a habit.

9. Critically Analyze What You Read and Hear

Don’t be swayed by vendors, media hype, or dogma.

10. It’s Both What You Say and the Way You Say It

There’s no point in having great ideas if you don’t communicate them effectively.

Code Organization

11. DRY – Don’t Repeat Yourself

Every piece of knowledge must have a single, unambiguous, authoritative representation.

12. Make It Easy to Reuse

If it’s easy to reuse, people will.

13. Eliminate Effects Between Unrelated Things

Design components that are self-contained and independent of each other.

14. There Are No Final Decisions

No decision is cast in stone. Always plan for change.

15. Use Tracer Bullets to Find the Target

Tracer bullets help you target precisely by focusing on the end goal and advancing cautiously with small, inexpensive steps.

Prototyping and Learning

16. Prototype to Learn

Prototyping is a learning experience. Its value lies in the lessons you learn.

17. Program Close to the Problem Domain

Design and code in your user’s language.

18. Estimate to Avoid Surprises

Estimate before you start. You’ll spot potential problems up front.

19. Iterate the Schedule with the Code

Use the experience you gain as you implement to refine the project time scales.

Tools and Environment

20. Keep Knowledge in Plain Text

Plain text won’t become obsolete.

21. Use the Power of Command Shells

Use the shell when graphical user interfaces don’t cut it.

22. Use a Single Editor Well

The editor should be an extension of your hand.

23. Always Use Source Code Control

Source code control is a time machine for your work.

24. Fix the Problem, Not the Blame

It doesn’t really matter whether the bug is your fault or someone else’s.

25. Don’t Panic When Debugging

Take a deep breath and consider what might be causing the issue.

Debugging and Problem Solving

26. “select” Isn’t Broken

The bug is most likely in the application, not the OS or compiler.

27. Don’t Assume It – Prove It

Prove your assumptions in the actual environment.

28. Learn a Text Manipulation Language

You spend a large part of each day working with text.

29. Write Code That Writes Code

Code generators increase your productivity and help avoid duplication.

Error Handling and Contracts

30. You Can’t Write Perfect Software

Software can’t be perfect. Protect your code and users from errors.

31. Design with Contracts

Use contracts to document and verify that code does what it claims.

32. Crash Early

A dead program usually does a lot less damage than a crippled one.

33. Use Assertions to Prevent the Impossible

Assertions validate your assumptions.

34. Use Exceptions for Exceptional Problems

Reserve exceptions for exceptional things.

35. Finish What You Start

The routine that allocates a resource should be responsible for deallocating it.

Design and Architecture

36. Minimize Coupling Between Modules

Avoid coupling by writing “shy” code.

37. Configure, Don’t Integrate

Implement technology choices as configuration options.

38. Put Abstractions in Code, Details in Metadata

Program for the general case.

39. Analyze Workflow to Improve Concurrency

Exploit concurrency in your user’s workflow.

40. Design Using Services

Design in terms of services behind well-defined interfaces.

41. Always Design for Concurrency

Allow for concurrency, and you’ll design cleaner interfaces.

42. Separate Views from Models

Gain flexibility at low cost by designing models and views.

43. Use Diagrams to Coordinate Workflow

Use diagrams to coordinate disparate facts and agents.

Programming Practices

44. Don’t Program by Coincidence

Rely only on reliable things. Beware of accidental complexity.

45. Estimate the Order of Your Algorithms

Get a feel for how long things are likely to take.

46. Test Your Estimates

Mathematical analysis doesn’t tell you everything.

47. Refactor Early, Refactor Often

Just as you might weed and rearrange a garden.

48. Design to Test

Start thinking about testing before you write a line of code.

49. Test Your Software, or Your Users Will

Test ruthlessly. Don’t make your users find bugs.

50. Don’t Use Wizard Code You Don’t Understand

Make sure you understand all the generated code.

Requirements and Planning

51. Don’t Gather Requirements – Dig for Them

Requirements rarely lie on the surface.

52. Work With a User to Think Like a User

It’s the best way to gain insight into how the system will really be used.

53. Abstractions Live Longer than Details

Invest in the abstraction, not the implementation.

54. Use a Project Glossary

Create and maintain a single source of all specific terms.

55. Don’t Think Outside the Box – Find the Box

When faced with an impossible problem, identify the real constraints.

56. Start When You’re Ready

You’ve been building experience all your life. Don’t ignore doubts.

57. Some Things Are Better Done than Described

Don’t fall into the specification spiral.

58. Don’t Mindlessly Adopt Formal Methods

Don’t unquestioningly adopt any technique.

59. Costly Tools Don’t Produce Better Designs

Judge tools on their merits.

Team and Project Management

60. Organize Teams Around Functionality

Don’t separate designers from coders. Build teams like you build code.

61. Don’t Use Manual Procedures

A script will execute the exact instructions every time.

62. Test Early. Test Often. Test Automatically

Tests that run with every build are much more effective.

63. Coding Ain’t Done ‘Til All the Tests Run

‘Nuff said.

64. Use Saboteurs to Test Your Testing

Introduce bugs intentionally to verify that testing catches them.

65. Test State Coverage, Not Code Coverage

Identify and test significant program states.

66. Find Bugs Once

Once a human tester identifies a bug, automated tests should verify it’s fixed from then on.

Documentation and Communication

67. English is Just a Programming Language

Write documents as you would write code.

68. Build Documentation In, Don’t Bolt It On

Documentation created separately from code is less likely to be correct.

69. Gently Exceed Your Users’ Expectations

Come to understand your users’ expectations, then deliver just that little bit more.

70. Sign Your Work

Artisans of an earlier age took pride in signing their work.

How to Use This List

Don’t try to implement all 70 tips at once. Focus on one principle for a week until it becomes second nature, then choose another.

Begin with core principles: care about your craft, consider your work, and avoid neglect. These will transform your approach.

The goal isn’t to be a perfect programmer but a pragmatic one—writing code that works, is maintainable, and solves real problems.

References