“An investment in knowledge always pays the best interest.” – Benjamin Franklin

Many of the best software developers have T-Shaped Skills: Deep expertise in programming and software development, and broad knowledge of diverse areas including testing, DevOps, UX design, team organization, customer interaction, and their domain areas. While there is unfortunately no substitute for experience, reading is probably the next best thing. Over the past 10 years I’ve read a lot in an effort to deepen and broaden my knowledge as a software developer. Along the way I’ve been organizing books and concepts into the reading list I share below. I have been trying to design a core curriculum for “modern” software development by asking myself:

What core concepts are required to be a world class software developer?

What is the best book for introducing and teaching each concept?

The result is a list of 16 essential software development concepts presented by 16 excellent books…

Methodology & Other Notes (skip to reading list)

Reading a book is a significant time investment, so I don’t recommend a book lightly. In order to design an effective and efficient reading list, I have tried to structure it around a concept list satisfying these criteria:

Complete : Don’t omit necessary concepts.

: Don’t omit necessary concepts. Essential : Don’t include optional concepts. Concepts should be relevant to most developers during their career.

: Don’t include optional concepts. Concepts should be relevant to most developers during their career. Non-redundant : Limit overlap and repetitive content.

: Limit overlap and repetitive content. Universal : Technology & domain independent.

: Technology & domain independent. Applied, not Theoretical : So this list excludes great theoretical books like Gödel, Escher, Bach.

: So this list excludes great theoretical books like Gödel, Escher, Bach. Software Development, not Programming : The distinction is quite fuzzy, but I think of programming as getting a computer to do what you want and software development as evolving valuable software with a team. Learning programming deserves an entirely different reading list (like this one). Every programming reading list should include Code Complete, SICP, and this article: Teach Yourself Programming in 10 years. (If you are learning programming, you may be interested to peruse my notes about Why & How I Write Java.)

: The distinction is quite fuzzy, but I think of programming as getting a computer to do what you want and software development as evolving valuable software with a team. Learning programming deserves an entirely different reading list (like this one). Every programming reading list should include Code Complete, SICP, and this article: Teach Yourself Programming in 10 years. (If you are learning programming, you may be interested to peruse my notes about Why & How I Write Java.) Software Development, not Computer Science: (The following observations are not a consensus. They are my opinions based on my experience which includes being a computer science teaching assistant for C++, assembly, logic design, algorithms, databases, and artificial intelligence, followed by 7 years of professional software development.) There is limited overlap between a professional software development (SD) curriculum and a traditional computer science (CS) curriculum. A computer science degree often includes learning programming plus courses including automata theory, algorithms, programming language theory, operating systems, networking, cryptography, databases, artificial intelligence, logic design, hardware architecture, etc. A functional understanding of algorithms, data structures, and complexity (big O) is important for programming. A CS database course provides a theoretical foundation for the broader SD challenge of data management in practice. Other than algorithms and databases, I’ve found many CS subjects to be optional (but occasionally helpful) for many kinds of software development. In some areas of SD a background in UX design or domain knowledge might actually be more helpful than CS.

Other notes:

Beyond listing the 16 core books for each concept, this page also include notes, links to further worthwhile reading, and related quotes. Some of the notes may not be useful until after reading the book for context.

I also link to other reading lists at the bottom of this page.

I prefer books with human narrative over a lofty academic writing style. I find that staying awake significantly improves my reading comprehension.

The book links are to Amazon where you can read reviews and view the table of contents by clicking on the book image. I assume many are available as eBooks. Some are also available online as PDFs.

My software development experience has mostly been in small startups delivering web applications and web services implemented in Java, Python, and JavaScript. As with any point of view, my experience informs this reading list while also biasing it.

By its nature this is an evolving list. I would love your feedback about the concept list, the book selections, my concept notes, and any worthwhile books or quotes that I’ve missed. Thanks!

Outline: A Software Developer’s Reading List

Software Creation Concepts

1. Test-Driven Development: Growing Object-Oriented Software, Guided By Tests

2. Refactoring: Refactoring

Software Design Concepts

3. Code Design: Clean Code

4. Component Design (Object-Oriented): Object Design

5. Domain Design: Domain Driven Design

6. System Design: Software Systems Architecture

7. System Design (Availability): Release It!

8. System Design (Security): Foundations of Security

9. Legacy Code: Working Effectively With Legacy Code

10. User Experience Design: Designing Interfaces

Software Delivery Concepts

11. Lean Systems: Leading Lean Software Development

12. Agile Team Organization: Essential Scrum

13. Requirements Design: User Stories Applied

14. Testing Products & Services: Agile Testing

15. Continuous Delivery: Continuous Delivery

16. Customer Development: Lean Startup

I believe these 16 books are a great start for any professional software developer (after learning programming). It is certainly a much more direct path than I took. By no means are these the only excellent books or the only books you should read. This list is simply the most effective and efficient overview I know how to recommend. The “Further Reading” sections contain many important and classic books.

The reading list begins with 2 basic software evolution skills: Test-Driven Development and Refactoring. Next are 4 different levels of software design granularity: Code Design, Component Design, Domain Design, and System Design. We then expand on System Design by digging into 2 commonly important system qualities: Availability and Security. We round out Software Design with 2 more perspectives: Legacy Code and User Experience Design. Next are the 6 Software Delivery concepts present how to organize tools & teams to successfully deliver valuable software to customers and users.

I have tried to place the books in a natural sequence, but I would read them in whatever order makes sense for you and your work. Many of these books are worth reading cover to cover, but some have sections you can safely skip or defer. (Perhaps keep in mind that if you have time to read 1000 pages, reading 200 pages of 5 books is often worth more than reading 500 pages of 2 books.)

New concepts based on feedback

I think it’s important to keep this list to a manageable size, however based on feedback, I will probably be adding 4 concepts:

Professional Craft : Software development is a craft. This has implications for how it is learned and what constitutes professional conduct. The classic book here would be The Pragmatic Programmer .

The Pragmatic Programmer Component Design (Functional) : I want to split Component Design into Component Design (Object-Oriented) and Component Design (Functional). It looks like Functional Thinking may be the right functional book when it is released (earlier articles and presentation by the author).

: I want to split Component Design into Component Design (Object-Oriented) and Component Design (Functional). It looks like Functional Thinking may be the right functional book when it is released (earlier articles and presentation by the author). Data Management : This is a core skill that is missing from the 16 concepts. I’m not sure what book to use. Subjects include relational vs. key-value, normalization vs. performance, transactions, big data, sharding, consistency, distribution, UUIDs, backup+recovery, scaling, schema evolution, etc. One option is a computer science textbook like Database Systems, but I worry about this choice because there is a sizable gap between theory and practice in this area.

: This is a core skill that is missing from the 16 concepts. I’m not sure what book to use. Subjects include relational vs. key-value, normalization vs. performance, transactions, big data, sharding, consistency, distribution, UUIDs, backup+recovery, scaling, schema evolution, etc. One option is a computer science textbook like Database Systems, but I worry about this choice because there is a sizable gap between theory and practice in this area. Managing Humans: Peopleware is the classic book (3rd edition in 2013). I stole the concept name from the book Managing Humans, which will be in the further reading section (along with the associated blog Rands in Repose). I would also include a link to this article.

Software Creation Concepts (#1-2)

1. Test-Driven Development: Writing automated tests to discover requirements, guide software design, enable refactoring, and prevent regressions.

Concept notes (this concept has more notes than the others):

Testing software early is important for many reasons: The cost of an undetected defect rises dramatically over time. Once automated tests are in place we find many bugs shortly after they are introduced, so we easily know what changes caused the problem. Writing tests helps us understand (and often discover) the detailed requirements. Tests enable us to improve the design of software via refactoring. The fear of changing untested software is worth experiencing: it fosters intrinsic motivation for testing. Testing improves modularity by forcing us to invert dependencies (statically depend on interfaces, not implementations). Tests provide executable requirements documentation, enabling us to write less textual documentation (trust code, not comments). Tests force us to support a 2nd client: production is the 1st, and the test is the 2nd. A 2nd example provides valuable design feedback. Testing guides us to write testable code. Untested code is often untestable. You end up with coupled Legacy Code.

When practicing Test-Driven Development you write tests before implementing the functionality: Before adding a new capability or fixing a bug, write a failing test. Make the test pass in the easiest conceivable way. Don’t plan much; design as little as possible; just get the test passing. If the implementation is embarrassing (but passing), you’re doing well at this point. Under the cozy protection of a passing test, refactor the implementation until it is “reasonably clean“.

There are many types of software testing including unit testing, integration testing, acceptance testing, system testing, regression testing, smoke testing, non-functional testing, stress testing, performance testing, usability testing, etc. Unit testing is the foundation. Here is a comparison of unit, integration, functional, and acceptance.

Most pieces of software (i.e. units, components, systems) use and depend on other pieces software. These other pieces of software are the dependencies. An important part of testing software is deciding what implementation to choose for dependencies during testing. For example, if you were persisting Account objects you would use (depend on) a database. Let’s say you use a MySQL database implementation in production. If you also use MySQL in your unit tests, you will often find that it is too slow and inconvenient. Instead you will want to use an in-memory database when running unit tests. This in-memory database is an example of a Test Double, or more specifically a Fake Object. With vocabulary derived from Mocks aren’t Stubs and the xUnit Test Patterns (table), here are several options for dependency implementations: Real Dependency : Using the same implementation in tests as in production. (MySQL in the example.) Test Double : Using a different implementation in tests: Fake Object : A different but fully functional implementation (in-memory database in the example) Dummy Object : An object that is passed around, but is never used by the code we’re testing (i.e. providing null, knowing it doesn’t matter) Stub Object : An object that provides canned (hardcoded) answers to method calls. Mock Object : From wikipedia, a mock is a simulated object that mimics the behavior of real objects in controlled ways. This is an extremely flexible Test Double, but the tradeoff is your test ends up knowing implementation details of the code you’re testing (coupling the two). For this reason I use fakes instead of mocks whenever possible. My favorite mocking library for Java is Mockito. (When deciding between mocks and fakes, perhaps also consider that mocks nail down behavior where fakes may only nail down end state. If you loop through rows in a database instead of doing one large query, the final state might be correct, but the looping implementation may be a behavioral bug causing inefficiency. In this case the test may want to verify the implementation details, despite the reduced modularity between test and code-under-test.)

In order to use a Test Double implementation for a dependency, that dependency must be inverted. Instead of statically importing an implementation (i.e. class or instance), the implementation should be provided at runtime, often as a constructor parameter. In a language with interfaces you would statically import the interface. By inverting the dependency, the test is able to provide a Test Double (i.e. in-memory database) while production provides the Real Dependency (i.e. MySQL database). It think it is fair to say that the unavoidable price of testability is dependency inversion. The family of mechanisms used to provide dependencies at runtime is sometimes called Dependency Injection.

Further reading:

Related quotes:

2. Refactoring: Evolving the design of software.

Concept notes:

Further reading:

Related quotes:

Software Design Concepts (#3-10)

3. Code Design: Writing intention revealing code.

Concept notes:

Professional code is written for humans to read. It is a communication medium. (The code itself, not just comments. Comment the why; the code should make what and how evident.) The person reading a developer’s code may be their teammate, or it may be themselves months or years from now. Coding is like writing or speaking: we learn what minimum level of clarity is necessary to communicate successfully. We meet this baseline of clarity even when we’re in a hurry (i.e. email, voicemail, rapid prototype). When the message is important we put extra effort into clarity (i.e. essay, speech, reusable library). The reality is that if someone is incoherent then nobody will want to listen to them (or work with them).

I feel that programming becomes software development when you collaborate with a team on a shared codebase. For a team to avoid accidental complexity, established standards should often take precedence over personal preferences, with all team members helping to guide what those standards should be. Pair programming is a great way to discuss, debate, and share such standards.

Further reading:

Related quotes:

4. Component Design (Object-Oriented): Managing interfaces to manage complexity.

(Update: When I do a full revision of this reading list, I think I’m going to switch my top recommendation in this category to Practical Object-Oriented Design in Ruby.)

Concept notes:

There are many great Component Design books, and it’s definitely worth reading several in this area. To get started I recommend Object Design because I think roles, responsibilities, and collaborations are at the heart of the matter, and because it reads like a human narrative. (One aspect of this book I’m unsure of is CRC cards because I’ve never tried them.)

Here is a mental model I’ve developed over time for how a bunch of component design principles (heuristics) relate to each other.

As a programming subject, the dichotomy between Functional Programming and Object-Oriented Programming isn’t addressed in this software development focused reading list. That being said, mutation and side-effects are complexity creating monsters. I believe that regardless of the implementation language, the best software designers apply functional principles when possible. I’ve never used a functional language in production, but I’ve benefited enough from these functional principles to consider them beyond doubt: Objects should be immutable by default. Passing Value Objects between layers is quite pleasant. Maximize the % of code that is referentially transparent. When the application design is Object-Oriented, much of the implementation can still be functional.



Further reading:

Related quotes:

5. Domain Design: Molding software to fit its domain.

Concept notes:

This unique book introduces the importance of discovering a Ubiquitous Language for discussing the domain with customers/users/experts, as well for naming the nouns and verbs in the software representation of the Domain Model. It also places the Domain Model at the center of our system, with the persistence (Repositories) and distribution (Application API) at the edges.

If you are used to web application frameworks like Rails or Django, it may seem unusual to have a persistence independent Domain Model. These frameworks can lead you towards using ORM objects as your Domain Model, which couples your persistence details and your Domain Model (at least they led me down this path). Essentially you trade modularity for getting started quickly, which may or may not be profitable based on how large and long lived your system is. Bob Martin gave a great presentation about what an alternative architecture looks like: centered around the Domain Model, with persistence details hidden behind Repository interfaces, and with delivery details (like HTTP) decoupled from the Domain as well. I prefer this alternative architecture, but modularity can increase the number of layers, which can have downsides such as increased learning time and worse performance due to layer translation (but if disk or network is your bottleneck, often this won’t matter).

Further reading:

Related quotes:

6. System Design: Holistic views of a system’s structure and qualities.

Concept notes:

Books about System Design & Software Architecture generally describe how to think about, work with, and document systems. Then they hone in on the most commonly important system quality attributes (non-functional requirements). There are dozens of quality attributes and their importance varies from system to system.





Software Systems Architecture has chapters dedicated to Security, Performance & Scalability, Availability & Resilience, and Evolution.





Software Architecture in Practice is a standard textbook (and a potential alternative System Design book), with chapters dedicated to Availability, Interoperability, Modifiability, Performance, Security, Testability, and Usability.





ISO 9126 is an international standard for the evaluation of software quality (more description here). Its quality model contains these characteristics and sub-characteristics: Functionality : Suitability, Accuracy, Interoperability, Security Reliability : Maturity, Fault tolerance, Recoverability Usability : Understandability, Learnability, Operability, Attractiveness Efficiency : Time Behavior, Resource Utilization Maintainability : Analyzability, Changeability, Stability, Testability Portability : Adaptability, Installability, Co-Existence, Replaceability



Further reading:

Related quotes:

7. System Design (Availability): Designing for environmental variation and failure conditions.

Concept notes:

As a system attribute, Availability is discussed as part of the System Design concept. However Availability is important enough to justify its own book on the reading list, especially when a book as excellent and practical as Release It! exists. There are many system quality attributes that contribute to Availability. They are interrelated and it can be difficult to keep the terminology straight (at least it is for me). Here is my attempt to provide definitions and relationships in a way that is consistent with Release It!:

Stability : Ability to continue handling requests in the face of component failures or a workload that exceeds capacity. You may not handle all requests, and handling quality may be degraded. Request : An abstract unit of work. Workload : A set or stream of requests. Mixed Workload : A combination of different types of requests. Redundancy : No single point of failure for capabilities or data.

: Ability to continue handling requests in the face of component failures or a workload that exceeds capacity. You may not handle all requests, and handling quality may be degraded. Scalability : Ability to add capacity to a system. Capacity : For a workload, the max throughput with acceptable performance. Throughput : Number of requests served in a given time window. Performance : How long a single request takes.

: Ability to add capacity to a system. Availability: Handling all requests acceptably. Availability often depends on Stability and Scalability (among other things).

Further reading:

Related quotes:

8. System Design (Security): Protecting data & resources.

Concept notes:

As a system attribute, Security is discussed as part of the System Design concept. I was undecided about including an additional book focused on just Security. Security is universally important, but many Security books have too much depth to provide excellent ROI for many software developers. However when I discovered Foundations of Security it made the decision easier. This excellent book has a practical breadth and depth, making it worthwhile for most software developers.

Further reading:

Related quotes:

9. Legacy Code: What happens when a codebase is untested, large, and coupled? How can you fix it?

Concept notes:

I was initially undecided about whether Legacy Code was relevant to most software developers. Some of us are lucky enough to not work with a legacy codebase. I decided to include this excellent book because it provides a view into the implications of technical debt over time. Every software developer benefits from knowing the implications of design decisions and being able to communicate them. This book also provides a toolkit for digging yourself out of trouble. Even if you don’t work with a legacy codebase, there are usually dark corners that need to be managed carefully. For example, if you decide to build on a prototype instead of throwing it away, you may be starting with some amount of Legacy Code.

Related quotes:

“To me, legacy code is simply code without tests. I’ve gotten some grief for this definition… I have no problem defining legacy code as code without tests. It is a good working definition, and it points to a solution.” – Michael Feathers (Legacy Code)

“The legacy code was so tightly coupled that if you put a chunk of coal between the classes you would get a diamond.” – Jeremy Miller

Further reading:

10. User Experience Design: How do users experience our products & services?

Concept notes:

Further reading:

UX Design is an entire profession, so reading a single patterns book only scratches the surface. There are many excellent and non-redundant books:

Related quotes:

Software Delivery Concepts (#11-16)

11. Lean Systems: Organizing an efficient & adaptive human system.

Concept notes:

An argument could be made that Lean Systems and Agile Team Organization are too similar to warrant separate treatment on this reading list. However, I include Lean Systems separately because it provides the big picture motivation for just-in time adaptive workflows like Agile, including the historical manufacturing context. Lean Systems sets the stage, and Agile Team Organization outlines a way to establish a Lean System for software product delivery.

Further reading:

Related quotes:

12. Agile Team Organization: Organizing incremental & iterative incremental product development.

Concept notes:

Agile is a set of methodologies for responding to these and other realities: Like writing an essay, software development is an creative learning process. The most important input to this learning is customer feedback, so you need to deliver working software to customers early & often. Future You is a genius compared to Current You, so defer decisions when possible. Any plans made today will be made with the least information we will ever have. Handoffs don’t work well. Too much work in progress causes problems.

I think the Agile Development and Lean Systems communities correctly describe many of the challenges of evolving valuable software with a team. I also think their proposed solutions are generally in the right direction. Not everyone agrees. I wouldn’t say everyone must practice Agile & Lean, but I would say that every professional should know the problems they address and the solutions they propose. You can pick and choose solutions based upon preferences and context. (For example, I am a bit skeptical of Velocity because I am regularly baffled by how difficult it is to predict what issues will arise during development.)

Scrum is the most popular flavor of Agile, but you’ll naturally want to choose an alternative book if you’re going to practice a different flavor (i.e. Extreme Programming).

I find Scrum to be a list of sensible and low ceremony practices: Sprints : Build software incrementally and iteratively. Deliver working features so you can get customer feedback early and often. User Stories : Write down ideas to work on and prioritize them. Defer figuring out the details (when this is responsible) because you’ll know more later. Scrum Meetings : Regularly talk as a team about what is going on. Sprint Reviews : Regularly show people what you are building to get feedback. Sprint Retrospectives : Regularly discuss how you can work better as a team. Product Owner : Have someone who has the knowledge, authority, and availability to prioritize features. Scrum Master : Have someone focused on process improvement and coordination. This person should ask questions and employ coaching. They should not give orders.



Further reading:

Related quotes:

13. Requirements Design: Cross-functional teams collaboratively designing software.

Concept notes:

Although the terms requirements and specification are not perfectly defined, here is a common and useful distinction: Requirements : From the customer and user perspective, what constraints should a product or service satisfy? What should the system do? Specification : From the engineering perspective, how will the requirements be satisfied? How will the system be implemented?



Questions for product development teams to answer include: How should requirements be represented? At what level of detail? When should they be written? In Agile projects, User Stories are light-weight goals serving as reminders to have a just-in-time design discussion. After a design discussion occurs, should the conclusions be recorded? Agile’s focus on conversations and working software over documentation suggests that you can often jump into writing tests and coding the feature (after breaking the story into smaller implementation tasks). But what if the design discussion involves complexities such as decision trees, workflows, timing, or other business logic? Then medium-weight representations such as Use Cases (Writing Effective Use Cases) and UML Diagrams (UML Distilled) can help support collaboration and mutual understanding. In contrast, heavy-weight requirements documents are more along the lines of Big Design Up Front.



Regarding User Stories vs. Use Cases, here is Alistair Cockburn’s description of when he employs Use Cases on Agile projects. (User Stories Applied also includes a comparison of User Stories and Use Cases.)



Ideally requirements are written and prioritized by the customer, or with as much of their input as possible. Writing requirements is a software design activity, so software developers can help guide the process by explaining: How to structure stories. When a story is too large for an iteration (or too small). How to split up stories by feature rather than layer (thus delivering working features for each story). How to avoid specifying user interface details, because requirements should only constrain outcomes (not how outcomes are achieved).



Further reading:

Related quotes:

14. Testing Products & Services: Do our products & services provide what our customers need?

Concept notes:

Testing products & services requires a diverse skill set. I believe software developers should strive to do as much of this as possible themselves. Books like Agile Testing and How Google Tests Software point the way. However these books also make it clear that several categories of high value testing and clarification are most effectively pursued by dedicated testing experts. Agile Testing is structured around Brian Marick’s testing quadrants:

Technology-Facing, Supporting the Team: Unit Tests, Component Tests Business-Facing, Supporting the Team: Functional Tests, Examples, Story Tests, Prototypes, Simulations Business-Facing, Critiquing the Product: Exploratory Testing, Scenarios, Usability Testing, User Acceptance Testing (UAT), Alpha/Beta Technology-Facing, Critiquing the Product: Performance & Load Testing, Security Testing, “ility” Testing

Further reading:

Related quotes:

“Testing a product is a learning process.” – Brian Marick (Testing Quadrants, Agile Manifesto)





“A pinch of probability is worth a pound of perhaps.” – James Thurber

15. Continuous Delivery: Automated pipeline for integration, testing, & release.

Concept notes:

Continuous Delivery is a broad concept that relates to Continuous Integration, Automated Testing, Configuration Management, Automated Deployment, Release Management, DevOps, and much more. Combining these practices into a (fully or mostly) automated delivery pipeline is essential to establishing a lean/agile/just-in-time/adaptive software delivery workflow. The actual release schedule is a business decision, but we should be technically able to release after every sprint (if not every day). Here is a sample set of Delivery/DevOps tools for a Java web application:

Further reading:

Related quotes:

16. Customer Development: Creating customer value.

Concept notes:

Further reading:

Related quotes:

One last note: Learning a craft requires a balance between studying and doing.

Other Software Development Reading Lists

Be sure to note the dates on some of these.