Code That Fits in Your Head
Code That Fits in Your Head
—Enrico Campidoglio,
independent consultant,
speaker and Pluralsight author
Mark Seemann
The author and publisher have taken care in the preparation of this book, but
make no expressed or implied warranty of any kind and assume no responsibility
for errors or omissions. No liability is assumed for incidental or consequential
damages in connection with or arising out of the use of the information or
programs contained herein.
For information about buying this title in bulk quantities, or for special sales
opportunities (which may include electronic versions; custom cover designs; and
content particular to your business, training goals, marketing focus, or branding
interests), please contact our corporate sales department at
[email protected] or (800) 382-3419.
For questions about sales outside the U.S., please contact [email protected].
ISBN-13: 978-0-13-746440-1
ISBN-10: 0-13-746440-1
ScoutAutomatedPrintCode
To my parents:
My mother, Ulla Seemann, to whom I owe my attention to
detail.
My father, Leif Seemann, from whom I inherited my
contrarian streak.
“The future is already here — it’s just not very evenly
distributed”
—William Gibson
Contents
PART I Acceleration
Chapter 2 Checklists
2.1 An Aid to Memory
2.2 Checklist for a New Code Base
2.2.1 Use Git
2.2.2 Automate the Build
2.2.3 Turn On all Error Messages
2.3 Adding Checks to Existing Code Bases
2.3.1 Gradual Improvement
2.3.2 Hack Your Organisation
2.4 Conclusion
Chapter 5 Encapsulation
5.1 Save the Data
5.1.1 The Transformation Priority Premise
5.1.2 Parametrised Test
5.1.3 Copy DTO to Domain Model
5.2 Validation
5.2.1 Bad Dates
5.2.2 Red Green Refactor
5.2.3 Natural Numbers
5.2.4 Postel’s Law
5.3 Protection of Invariants
5.3.1 Always Valid
5.4 Conclusion
Chapter 6 Triangulation
6.1 Short-Term versus Long-Term Memory
6.1.1 Legacy Code and Memory
6.2 Capacity
6.2.1 Overbooking
6.2.2 The Devil’s Advocate
6.2.3 Existing Reservations
6.2.4 Devil’s Advocate versus Red Green
Refactor
6.2.5 When Do You Have Enough Tests?
6.3 Conclusion
Chapter 7 Decomposition
7.1 Code Rot
7.1.1 Thresholds
7.1.2 Cyclomatic Complexity
7.1.3 The 80/24 Rule
7.2 Code That Fits in Your Brain
7.2.1 Hex Flower
7.2.2 Cohesion
7.2.3 Feature Envy
7.2.4 Lost in Translation
7.2.5 Parse, Don’t Validate
7.2.6 Fractal Architecture
7.2.7 Count the Variables
7.3 Conclusion
Chapter 9 Teamwork
9.1 Git
9.1.1 Commit Messages
9.1.2 Continuous Integration
9.1.3 Small Commits
9.2 Collective Code Ownership
9.2.1 Pair Programming
9.2.2 Mob Programming
9.2.3 Code Review Latency
9.2.4 Rejecting a Change Set
9.2.5 Code Reviews
9.2.6 Pull Requests
9.3 Conclusion
Chapter 12 Troubleshooting
12.1 Understanding
12.1.1 Scientific Method
12.1.2 Simplify
12.1.3 Rubber Ducking
12.2 Defects
12.2.1 Reproduce Defects as Tests
12.2.2 Slow Tests
12.2.3 Non-deterministic Defects
12.3 Bisection
12.3.1 Bisection with Git
12.4 Conclusion
Chapter 14 Rhythm
14.1 Personal Rhythm
14.1.1 Time-Boxing
14.1.2 Take Breaks
14.1.3 Use Time Deliberately
14.1.4 Touch Type
14.2 Team Rhythm
14.2.1 Regularly Update Dependencies
14.2.2 Schedule Other Things
14.2.3 Conway’s Law
14.3 Conclusion
Bibliography
Index
Series Editor Foreword
I’ve known Mark Seemann for quite a few years now. I don’t
remember ever actually meeting him. It may be that we
have never actually been together in the same room. But he
and I have interacted quite a bit in professional newsgroups
and social networks. He’s one of my favourite people to
disagree with.
—Robert C. Martin
Preface
Prerequisites
This isn’t a beginner’s book. While it deals with how to
organise and structure source code, it doesn’t cover the
most basic details. I expect that you already understand
why indentation is helpful, why long methods are
problematic, that global variables are bad, and so on. I don’t
expect you to have read Code Complete [65], but I assume
that you know of some of the basics covered there.
Organisation
While this is a book about methodologies, I’ve structured it
around a code example that runs throughout the book. I
decided to do it that way in order to make the reading
experience more compelling than a typical ‘pattern
catalogue’. One consequence of this decision is that I
introduce practices and heuristics when they fit the
‘narrative’. This is also the order in which I typically
introduce the techniques when I coach teams.
Acknowledgements
I’d like to thank my wife Cecilie for love and support during
all the years we’ve been together, and my children Linea
and Jarl for staying out of trouble.
I’d also like to thank Adam Tornhill for his feedback on the
section about his work.
I’m indebted to Dan North for planting the phrase Code That
Fits in Your Head in my subconscious, which might have
happened as early as 2011 [72].
Once you’ve built a house, people can move in. You’ll need
to maintain it, but the cost of that is only a fraction of what
it cost to build it. Granted, software like that exists.
Particularly in the enterprise segment, once you’ve built2 an
internal line-of-business application, it’s done and users are
fettered to it. Such software enters maintenance mode once
the project is done.
2. This is a verb I aspire to never use about software development, but in this
particular context, it makes sense.
But most software isn’t like that. Software that competes
with other software is never done. If you’re caught in the
house-building metaphor, you may think of it as a series of
projects. You may plan to release the next version of your
product in nine months, but to your horror find that your
competitor publishes improvements every three months.
1.1.3 Dependencies
When you build a house, physical reality imposes
constraints. You must first lay the foundations, then raise
the walls, and only then can you put on the roof. In other
words, the roof depends on the walls, which depend on the
foundation.
1.3.2 Heuristics
My software craftsmanship years were, in some sense, a
period of utter disillusionment. I saw skill as nothing but
accumulated experience. It seemed to me that there was no
methodology to software development. That everything
depended on circumstances. That there was no right or
wrong way to do things.
1.4 Conclusion
If you think about the history of software development, you
probably think of advances at orders of magnitudes. Yet,
many of those advances are advances in hardware, not
software. Still, in the last fifty years, we’ve witnessed
tremendous progress in software development.
The good news is that you can do many of the things that
engineers do. There’s a mindset, and a collection of
processes you can follow.
The problem isn’t that you don’t know how to do a thing; it’s
that you forget to do it, even though you know that you
ought to.
Exactly how to use Git, automate the build, and turn on all
error messages is up to you, but in order to make the above
checklist concrete, I’ll show you a detailed, running
example.
$ git init
That’s it. You may also consider following the advice from
my friend Enrico Campidoglio [17] and add an empty
commit:
When you run the web site, it serves a single text file with
the contents:
Hello World!
The goal in this step is to automate the build. While you can
open your IDE and use it to compile the code, that’s not
automatable. Create a script file that performs the build,
and commit that to Git as well. Initially, it’s as simple as
listing 2.3 shows.
I spend all of my command-line time in Bash, despite
working in Windows, so I’ve defined a shell script. You can
create a .bat file, or a PowerShell script instead, if that’s
more to your liking7. The important part is that right now, it
calls dotnet build. Notice that I’m configuring a Release
build. The automated build should reflect what will
eventually go into production.
7. If you need to do something more complex, such as assemble documentation,
compile reusable packages for package managers, and so on, you may
consider a full-blown build tool. But start simple, and only add complexity if
you need it. Often, you don’t.
As you add more build steps, you should add them to the
build script as well. The point of the script is that it should
serve as a low-friction tool that developers can run on their
own machine. If the build script passes on a developer’s
machine, it’s OK to push the changes to the Continuous
Integration server.
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello
World!");
});
});
}
}
#!/usr/bin/env bash
dotnet build --configuration Release
I had a fairly good idea about what the problem was, but I
try to help people by letting them discover things for
themselves. You learn better that way.
The change to the Program class is that it’s now marked with
the static keyword. There’s no reason for a class to support
instantiation when it has only shared members. That’s an
example of a code analysis rule. It’s hardly of much import
here, but on the other hand, the fix is as simple as adding a
single keyword to the class declaration, so why not follow
the advice? In other cases, that rule can help you make the
code base easier to understand.
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello
World!")
.ConfigureAwait(false);
});
});
}
}
Is that engineering? Is that it? It’s not all you can do, but it’s
a good first step. Engineering, broadly speaking, is to use all
the heuristics and deterministic machinery you can to
improve the chance of ultimate success. Those tools are like
automated checklists. Every time you run them, they control
for thousands of potential issues.
Some of them have been around for a long time, but in my
experience few people use them. The future is unevenly
distributed. Turn the controls on. Improve the outcome with
no increase in skill.
You can do the same with linters and analysers. With .NET
analysers, for example, you can configure which rules to
enable. Address one rule at a time, and once you’ve
eliminated all warnings produced by a given rule, turn that
rule on so that it prevents all future instances.
The key, in all cases, is to follow the Boy Scout Rule [61]:
leave the code in a better state than you found it.
2.4 Conclusion
Checklists will improve outcomes with no increase in skill
[40]. Use them. Checklists help you remember to make the
right decisions. They support you; they don’t control you.
You saw how to enable Git right away. That’s the simplest of
the three items on the checklist. When you consider how
easy it is to take that step, though, that small effort pays
manifold.
You also saw how to automate the build. This, too, is easy to
do when you do it right away. Have a build script. Use it.
In the rest of the book, you’ll see the impact these early
decisions have on the code base as I add features.
A baseball bat and ball costs $1.10. The bat costs a dollar
more than the ball. How much does the ball cost?
3.1 Purpose
After reading the first two chapters, you may be
underwhelmed. Perhaps you thought that software
engineering was going to be a cerebral, sophisticated,
arcane, and esoteric discipline. We can easily make it more
sophisticated than what you’ve seen so far, but we have to
start somewhere. Why not start with the easy parts? As
figure 3.1 alludes, climbing a hill starts at ground level.
3.1.1 Sustainability
An organisation creates software for various reasons. Often,
it’s to make money. Sometimes, it’s to save money. Once in
a while, governments institute software projects to supply
digital infrastructure for its citizens; there are no direct
profits or savings to be gained from the software, but
there’s a mission to fulfil.
3.1.2 Value
Software exists to serve a purpose. It should provide value. I
often run into software professionals who seem blinded by
that word. If the code you wrote doesn’t provide value, then
why did you write it?
This book uses the number seven as a token for the limit of
the brain’s short-term memory. You may be able to keep
track of nine things from time to time, but seven represents
the concept well.
You write a line of code once and read it multiple times [61].
You rarely get to work with a pristine code base. When you
work with an existing code base, you must understand it
before you can successfully edit it. When you add a new
feature, you read the existing code to figure out how to best
reuse what’s already there and to learn what new code
you’ll have to add. When you struggle to fix a bug, you must
first understand what causes it. You’ll typically spend the
majority of your programming time reading existing code.
It’s the wrong answer. If the ball costs 10 cents then the bat
must cost $1.10, and the total price would be $1.20. The
correct answer is 5 cents.
I have. Not that I’ve literally fallen asleep behind the wheel,
but I’ve been so lost in thought that I’ve been oblivious that
I was driving. I’ve also accomplished the feat of bicycling
past my own home, as well as trying to unlock the door to
my downstairs neighbour instead of my own.
After one of the incidents where I’d found myself behind the
wheel of my car, wondering how I got where I was, I was as
astounded as I was appalled. I’d been driving in my home
city of Copenhagen, and I must have performed a series of
complex manoeuvres to get where I was. Stopping for red,
turning left, turning right without hitting any of the city’s
omnipresent bicyclists, correctly navigating to my
destination. Yet I had no recollection of doing any of that.
Your conscious awareness isn’t a required ingredient for
complex intellectual work.
System 2 allocates attention to the effortful mental activities that demand it,
including complex computations. The operations of System 2 are often
associated with the subjective experience of agency, choice, and
concentration.” [51]
The measure of success for System 1 is the coherence of the story it manages
to create. The amount and quality of the data on which the story is based are
largely irrelevant. When information is scarce, which is a common occurrence,
System 1 operates as a machine for jumping to conclusions.” [51]
3.4 Conclusion
The core problem that software engineering should solve is
that it’s so complex that it doesn’t fit the human brain. Fred
Brooks offered this analysis in 1986:
“Many of the classical problems of developing software products derive from
this essential complexity and its nonlinear increases with size [...] From the
complexity comes the difficulty of enumerating, much less understanding, all
the possible states of the program” [14]
I use the term complexity in the same way that Rich Hickey
uses it [45]: as an antonym to simplicity. Complex means
‘assembled from parts’, as opposed to simple, which implies
unity.
Perhaps you recoil from all of this. You may think that it’s
going to slow you down.
4. Type-driven development
. Property-driven development2
2. See subsection 15.3.1 for an example of property-based testing.
Once you’ve done that, add your first test case, like listing
4.1.
[Fact]
public async Task HomeIsOk()
{
using var factory = new WebApplicationFactory<Startup>();
var client = factory.CreateClient();
var response = await client
.GetAsync(new Uri("", UriKind.Relative))
.ConfigureAwait(false);
Assert.True(
response.IsSuccessStatusCode,
$"Actual status code: {response.StatusCode}.");
}
The test shown in listing 4.1 uses the xUnit.net unit testing
framework. This is the framework I’ll use throughout the
book. Even if you’re not familiar with it, it should be easy to
follow the examples, as it follows well-known patterns for
unit testing [66].
#!/usr/bin/env bash
dotnet test --configuration Release
The blank lines, on the other hand, are there because the
code follows the Arrange Act Assert pattern [9], also known
as the AAA pattern. The idea is to organise a unit test into
three phases.
. In the act phase you invoke the operation you’d like to test.
This only works if you can avoid additional blank lines in the
test. A common problem is when the arrange section grows
so big that you get the urge to apply some formatting by
adding blank lines. If you do that, you’ll have more than two
blank lines in your test, and it’s unclear which of them
delineate the three phases.
If the test code is so big that you must add additional blank
lines, you’ll have to resort to code comments to identify the
three phases [92], but try to avoid this.
Be aware that while it’s fine to turn off this particular rule for
unit tests, it should remain in effect for production code.
Listing 4.3 shows the Characterisation Test after clean-up.
does6. At the site where you create the object, there’s little
difference. Therefore, I think it’s fair to suppress the warning
since the code contains a string literal – not a variable.
6. Read more about guarantees and encapsulation in chapter 5.
Listing 4.3 Test with relaxed code analysis rules.
(Restaurant/d8167c3/Restaurant.RestApi.Tests/HomeTests.cs
)
Click here to view code image
[Fact]
[SuppressMessage(
"Usage", "CA2234:Pass system uri objects instead of
strings",
Justification = "URL isn't passed as variable, but as
literal.")]
public async Task HomeIsOk()
{
using var factory = new WebApplicationFactory<Startup>();
var client = factory.CreateClient();
Assert.True(
response.IsSuccessStatusCode,
$"Actual status code: {response.StatusCode}.");
}
4.3 Outside-in
Now we’re up to speed. There’s a system that responds to
HTTP requests (although it doesn’t do much) and there’s an
automated test. That’s our Walking Skeleton [36].
Figure 4.4 The plan is to create a vertical slice through
the system that receives a valid reservation and saves it
in a database.
Listing 4.4 Test that asserts that the home resource returns
JSON.
(Restaurant/316beab/Restaurant.RestApi.Tests/HomeTests.cs
)
Click here to view code image
[Fact]
[SuppressMessage(
"Usage", "CA2234:Pass system uri objects instead of
strings",
Justification = "URL isn't passed as variable, but as
literal.")]
public async Task HomeReturnsJson()
{
using var factory = new WebApplicationFactory<Startup>();
var client = factory.CreateClient();
Assert.True(
response.IsSuccessStatusCode,
$"Actual status code: {response.StatusCode}.");
Assert.Equal(
"application/json",
response.Content.Headers.ContentType?.MediaType);
}
9. You may have heard that a test should have only one assertion. You may also
have heard that having multiple assertions is called Assertion Roulette, and
that it’s a code smell. Assertion Roulette is, indeed, a code smell, but multiple
assertions per test isn’t necessarily an example of it. Assertion Roulette is
either when you repeatedly interleave assert sections with additional arrange
and act code, or when an assertion lacks an informative assertion message
[66].
[Route("")]
public class HomeController : ControllerBase
{
public IActionResult Get()
{
return Ok(new { message = "Hello, World!" });
}
}
app.UseRouting();
app.UseEndpoints(endpoints => {
endpoints.MapControllers(); });
}
}
When picking a feature for the first vertical slice, I look for a
few things. You could call this a heuristic as well.
When pursuing a vertical slice, aim for the happy path [66].
For now, ignore all the things that could go wrong10. The
goal is to demonstrate that the system has a specific
capability. In this example, the desired capability is to
receive and save a reservation.
10. But if you think of any, write them down so you don’t forget about them [9].
[Fact]
public async Task PostValidReservation()
{
var response = await PostReservation(new {
date = "2023-03-10 19:00",
email = "[email protected]",
name = "Katinka Ingabogovinanana",
quantity = 2 });
Assert.True(
response.IsSuccessStatusCode,
$"Actual status code: {response.StatusCode}.");
}
You may have noticed that the test delegates all the action
to a method called PostReservation. This is the Test Utility
Method [66] shown in listing 4.8.
[SuppressMessage(
"Usage",
"CA2234:Pass system uri objects instead of strings",
Justification = "URL isn't passed as variable, but as
literal.")]
private async Task<HttpResponseMessage> PostReservation(
object reservation)
{
using var factory = new WebApplicationFactory<Startup>();
var client = factory.CreateClient();
The only other remark I’ll make about listing 4.8 is that I
chose to suppress the code analysis rule that suggests Uri
objects, for the same reason as explained in section 4.2.3.
The first detail that you see is the ugly #pragma instructions.
As their comments suggest, they suppress a static code
analysis rule that insists on making the Post method static.
You can’t do that, though: if you make the method static
then the test fails. The ASP.NET MVC framework matches
HTTP requests with controller methods by convention, and
methods must be instance methods (i.e. not static).
All tests now pass. Commit the changes in Git, and consider
pushing them through your deployment pipeline [49].
4.3.3 Unit Test
As listing 4.9 shows, the web service doesn’t handle the
posted reservation. You can use another test to drive the
behaviour closer to the goal, like the test in listing 4.10.
[Fact]
public async Task PostValidReservationWhenDatabaseIsEmpty()
{
var db = new FakeDatabase();
var sut = new ReservationsController(db);
The act phase creates a Data Transfer Object (DTO) [33] and
passes it to the Post method. You could also have created
the dto as part of the arrange phase. I think you can argue
for both alternatives, so I tend to go with what balances
best, as I described in section 4.2.2. In this case there are
two statements in each phase. I think that this 2-2-2
structure balances better than the 3-1-2 shape that would
result if you move initialisation of the dto to the arrange
phase.
How do you think Reservation looks? Why does the code even
contain two classes with similar names? The reason is that
while they both represent a reservation, they play different
roles.
[SuppressMessage(
"Naming",
"CA1710:Identifiers should have correct suffix",
Justification = "The role of the class is a Test Double.")]
public class FakeDatabase :
Collection<Reservation>, IReservationsRepository
{
public Task Create(Reservation reservation)
{
Add(reservation);
return Task.CompletedTask;
}
}
[ApiController, Route("[controller]")]
public class ReservationsController
{
public ReservationsController(IReservationsRepository
repository)
{
Repository = repository;
}
await Repository
.Create(
new Reservation(
new DateTime(2023, 11, 24, 19, 0, 0),
"[email protected]",
"Julia Domna",
5))
.ConfigureAwait(false);
}
}
The code in listing 4.15 passes the test in listing 4.10, but
now another test fails!
4.3.8 Configure Dependencies
While the new test succeeds, the boundary test in listing 4.7
now fails because ReservationsController no longer has a
parameterless constructor. The ASP.NET framework needs
help creating instances of the class, particularly because no
classes in the production code implement the required
IReservationsRepository interface.
services.AddSingleton<IReservationsRepository>(
new NullRepository());
}
All tests now pass. Commit the changes in Git, and consider
pushing them through your deployment pipeline.
“Wait a minute,” you say, “it doesn’t work at all! It’d just
save a hard-coded reservation! And what about input
validation, logging, or security?”
4.4.1 Schema
How should we save the reservation? In a relational
database? A graph database [89]? A document database?
await conn.OpenAsync().ConfigureAwait(false);
await cmd.ExecuteNonQueryAsync().ConfigureAwait(false);
}
var connStr =
Configuration.GetConnectionString("Restaurant");
services.AddSingleton<IReservationsRepository>(
new SqlReservationsRepository(connStr));
}
builder.ConfigureServices(services =>
{
services.RemoveAll<IReservationsRepository>();
services.AddSingleton<IReservationsRepository>(
new FakeDatabase());
});
}
}
[SuppressMessage(
"Usage",
"CA2234:Pass system uri objects instead of strings",
Justification = "URL isn't passed as variable, but as
literal.")]
private async Task<HttpResponseMessage> PostReservation(
object reservation)
{
using var factory = new RestaurantApiFactory();
var client = factory.CreateClient();
Once more, all tests pass. Commit the changes in Git, and
push them through your deployment pipeline. Once the
changes are in production, perform another manual Smoke
Test against the production system.
4.5 Conclusion
A thin vertical slice is an effective way to demonstrate that
the software may actually work. Combined with Continuous
Delivery [49] you’re able to quickly put working software in
production.
You may think that the first vertical slice is so ‘thin’ that it’s
pointless. The example in this chapter showed how to save
a reservation in a database, but the values being saved
aren’t the values supplied to the system. How does that add
any value?
[Theory]
[InlineData(
"2023-11-24 19:00", "[email protected]", "Julia Domna",
5)]
[InlineData("2024-02-13 18:15", "[email protected]", "Xenia Ng",
9)]
public async Task PostValidReservationWhenDatabaseIsEmpty(
string at,
string email,
string name,
int quantity)
{
var db = new FakeDatabase();
var sut = new ReservationsController(db);
One thing that should bother you is that the assertion phase
of the test now seems to duplicate what would essentially
be the implementation code. That’s clearly not perfect. You
shouldn’t trust your brain to write production code without
some sort of double-entry bookkeeping, but that only works
if the two views are different. That’s not the case here.
Listing 5.2 The Post method now saves the dto data.
(Restaurant/4617450/Restaurant.RestApi/ReservationsContr
oller.cs)
Click here to view code image
5.2 Validation
With the code in listing 5.2, what happens if a client posts a
JSON document without an at property?
[Theory]
[InlineData(null, "[email protected]", "Jay Xerxes", 1)]
public async Task PostInvalidReservation(
string at,
string email,
string name,
int quantity)
{
var response =
await PostReservation(new { at, email, name, quantity
});
Assert.Equal(HttpStatusCode.BadRequest,
response.StatusCode);
}
You can easily pass the test with the code in listing 5.4. The
major difference from listing 5.2 is the addition of the Null
Guard.
[Theory]
[InlineData(null, "[email protected]", "Jay Xerxes", 1)]
[InlineData("not a date", "[email protected]", "Wk Hd", 8)]
[InlineData("2023-11-30 20:01", null, "Thora", 19)]
public async Task PostInvalidReservation(
string at,
string email,
string name,
int quantity)
{
var response =
await PostReservation(new { at, email, name, quantity
});
Assert.Equal(HttpStatusCode.BadRequest,
response.StatusCode);
}
Once you’ve moved through all three phases, you start over
with a new failing test. Figure 5.2 illustrates the process.
Figure 5.2 The Red Green Refactor cycle.
How could you have known that? I’m not sure that I can give
an answer that leads to reproducible results. I thought of
this refactoring because I knew the behaviour of
DateTime.TryParse. This is another example of programming
based on the shifting sands of individual experience [4]–the
art in software engineering.
Design by Contract
Listing 5.8 shows the same test method as listing 5.5, but
with two new test cases with invalid quantities.
[Theory]
[InlineData(null, "[email protected]", "Jay Xerxes", 1)]
[InlineData("not a date", "[email protected]", "Wk Hd", 8)]
[InlineData("2023-11-30 20:01", null, "Thora", 19)]
[InlineData("2022-01-02 12:10", "[email protected]", "3 Beard", 0)]
[InlineData("2045-12-31 11:45", "[email protected]", "Gil Tan",
-1)]
public async Task PostInvalidReservation(
string at,
string email,
string name,
int quantity)
{
var response =
await PostReservation(new { at, email, name, quantity
});
Assert.Equal(HttpStatusCode.BadRequest,
response.StatusCode);
}
These new test cases in turn drove the revision of the Post
method you can see in listing 5.9. The new Guard Clause [7]
only accepts natural numbers.
The code in listing 5.9 compiles and all tests pass. Commit
the changes in Git, and consider pushing them through your
deployment pipeline.
Users can easily give you a bogus email address that fits the
spec. The only way to really validate an email address is to
send a message to it and see if that provokes a response
(such as the user clicking on a validation link). That would
be a long-running asynchronous process, so even if you’d
want to do that, you can’t do it as a blocking method call.
The bottom line is that it makes little sense to validate the
email address, apart from checking that it isn’t null. For that
reason, I’m not going to validate it more than I’ve already
done.
Postel’s Law
[Theory]
[InlineData(
"2023-11-24 19:00", "[email protected]", "Julia Domna",
5)]
[InlineData("2024-02-13 18:15", "[email protected]", "Xenia Ng",
9)]
[InlineData("2023-08-23 16:55", "[email protected]", null, 2)]
public async Task PostValidReservationWhenDatabaseIsEmpty(
string at,
string email,
string name,
int quantity)
{
var db = new FakeDatabase();
var sut = new ReservationsController(db);
In the green phase, make the test pass. Listing 5.11 shows
one way to do that. You could have used a standard ternary
operator, but C#’s null coalescing operator (??) is a more
compact alternative. In a way, it replaces the ! operator, but
it’s a good trade-off, because ?? doesn’t suppress the
compiler’s null-check engine.
var r =
new Reservation(d, dto.Email, dto.Name ?? "",
dto.Quantity);
await Repository.Create(r).ConfigureAwait(false);
You can look at the Reservation class in listing 4.12 and see
that, indeed, Email is guaranteed to not be null, because
you’ve used the type system to declare it non-nullable. The
same is true for the date, but what about the quantity? Can
you be sure that it isn’t negative, or zero?
At the moment, the only way you can answer that question
is by some detective work. What other code calls the Create
method? Currently, there’s only one call site, but this could
change in the future. What if there were multiple callers?
That’s a lot to keep track of in your head.
[Theory]
[InlineData( 0)]
[InlineData(-1)]
public void QuantityMustBePositive(int invalidQantity)
{
Assert.Throws<ArgumentOutOfRangeException>(
() => new Reservation(
new DateTime(2024, 8, 19, 11, 30, 0),
"[email protected]",
"Marie Ilsøe",
invalidQantity));
}
I chose to parametrise this test method because I consider
the value zero fundamentally different from negative
numbers. Perhaps you think that zero is a natural number.
Perhaps you don’t. As with so many other things5 there’s no
consensus. Despite this, the test makes it clear that zero is
an invalid quantity. It also uses -1 as an example of a
negative number.
5. What’s a unit? What’s a mock?
In the red phase of Red Green Refactor this test fails. Move
to the green phase by making it pass. Listing 5.13 shows the
resulting constructor.
{
if (quantity < 1)
throw new ArgumentOutOfRangeException(
nameof(quantity),
"The value must be a positive (non-zero) number.");
At = at;
Email = email;
Name = name;
Quantity = quantity;
}
5.4 Conclusion
Encapsulation is one of the most misunderstood concepts of
object-oriented programming. Many programmers believe
that it’s a prohibition against exposing class fields directly –
that class fields should be ‘encapsulated’ behind getters
and setters. That has little to do with encapsulation.
Not only is legacy code difficult to work with. It’s also hard
to escape from.
6.2 Capacity
Software engineering should sustain the organisation that
owns the software. You develop sustainable code bases by
making sure that the code fits in your brain. The capacity of
your working memory is seven, so only a few things should
be going on at the same time.
6.2.1 Overbooking
Apart from minimal input validation, the current restaurant
system tolerates any reservation, in the future or in the
past, for any positive quantity. The restaurant that it
supports, however, has a physical capacity. Additionally, it
may already be fully booked on a given date. The system
should check a reservation against existing reservations and
the capacity of the restaurant.
Assert.Equal(
HttpStatusCode.InternalServerError,
response.StatusCode);
}
The next reservation for five people fails. As the name of the
test implies, the test case is an overbooking attempt. The
restaurant doesn’t have capacity for eleven people.
Implicitly, what the test tells us is that the restaurant
capacity is somewhere between six and ten.
if (dto.Email == "[email protected]")
return new StatusCodeResult(
StatusCodes.Status500InternalServerError);
var r =
new Reservation(d, dto.Email, dto.Name ?? "",
dto.Quantity);
await Repository.Create(r).ConfigureAwait(false);
You need to add at least one more test case to prompt the
correct implementation. Fortunately, a new test case is
often just a new line of test data in a Parametrised Test [66],
as you can see in listing 6.3.
That may not have been the test method you’d expected.
Perhaps you thought the new test case should have been
added to the OverbookAttempt method that we are ‘currently’
working with (listing 6.1). Instead, this is a fourth test case
for an ‘older’ test (PostValidReservationWhenDatabaseIsEmpty).
Why is that?
[Theory]
[InlineData(
"2023-11-24 19:00", "[email protected]", "Julia Domna",
5)]
[InlineData("2024-02-13 18:15", "[email protected]", "Xenia Ng",
9)]
[InlineData("2023-08-23 16:55", "[email protected]", null, 2)]
[InlineData("2022-03-18 17:30", "[email protected]", "Shanghai
Li", 5)]
public async Task PostValidReservationWhenDatabaseIsEmpty(
string at,
string email,
string name,
int quantity)
{
var db = new FakeDatabase();
var sut = new ReservationsController(db);
var reservations =
await
Repository.ReadReservations(d).ConfigureAwait(false);
if (reservations.Any())
return new StatusCodeResult(
StatusCodes.Status500InternalServerError);
var r =
new Reservation(d, dto.Email, dto.Name ?? "",
dto.Quantity);
await Repository.Create(r).ConfigureAwait(false);
Task<IReadOnlyCollection<Reservation>> ReadReservations(
DateTime dateTime);
}
return Task.FromResult<IReadOnlyCollection<Reservation>>(
this.Where(r => min <= r.At && r.At <= max).ToList());
}
Listing 6.7 Test that it’s possible to book a table even when
there’s already an existing reservation for the same date.
(Restaurant/bf48e45/Restaurant.RestApi.Tests/ReservationsT
ests.cs)
Click here to view code image
[Fact]
public async Task BookTableWhenFreeSeatingIsAvailable()
{
using var service = new RestaurantApiFactory();
await service.PostReservation(new
{
at = "2023-01-02 18:15",
email = "[email protected]",
name = "Ned Tucker",
quantity = 2
});
Assert.True(
response.IsSuccessStatusCode,
$"Actual status code: {response.StatusCode}.");
}
Yes, that’s possible, but it’s getting harder. Listing 6.8 shows
the relevant snippet from the Post method (i.e. not the entire
Post method). It uses LINQ to first convert the reservations to
a collection of quantities, and then picks only the first of
those.
var reservations =
await Repository.ReadReservations(d).ConfigureAwait(false);
int reservedSeats =
reservations.Select(r => r.Quantity).SingleOrDefault();
if (10 < reservedSeats + dto.Quantity)
return new StatusCodeResult(
StatusCodes.Status500InternalServerError);
The code in listing 6.9 still passes all tests, but is also more
general. That’s an improvement, so check the changes into
Git.
var reservations =
await
Repository.ReadReservations(d).ConfigureAwait(false);
int reservedSeats = reservations.Sum(r => r.Quantity);
if (10 < reservedSeats + dto.Quantity)
return new StatusCodeResult(
StatusCodes.Status500InternalServerError);
var r =
new Reservation(d, dto.Email, dto.Name ?? "",
dto.Quantity);
await Repository.Create(r).ConfigureAwait(false);
6.3 Conclusion
In geometry (and geographical surveys) triangulation is a
process to determine the location of a point. When used
about test-driven development it’s a loose metaphor.
When you test-drive a code base, the tests play the role of
measurements. What’s different is that when you add a test,
it measures something that isn’t there yet. That’s the
situation to the right in figure 6.4.
The more tests you add, the better you describe the System
Under Test, just as the more measurements you take in a
geographical survey, the more precisely can you determine
the target’s position. For this to work, though, you must shift
your perspective substantially between each measurement.
The more lines of code, the worse the code base. Lines of
code is only a productivity metric if you measure lines of
code deleted. The more lines of code you add, the more
code other people have to read and understand.
How small?
You can keep your line width in check with the help of code
editors. Most development environments come with an
option to paint vertical lines in the edit windows. You can,
for example, put a line at the 80-character mark.
There’s more than one way to measure that, but one option
is to use cyclomatic complexity. You could diagram the
capacity of your short-term memory like figure 7.3.
All ‘slots’ are filled. If you treat the number seven as a hard
limit5, then you can’t add more complexity to the Post
method. The problem is that in the future, you’re going to
have to add more complex behaviour. For example, you may
want to reject all reservations in the past. Also, the business
rule only works for hipster restaurants with communal
tables and single seatings. A more sophisticated reservation
system should be able to handle tables of different sizes,
second seatings, and so on.
5. The number seven isn’t really a hard limit. Nothing in the line of reasoning
presented here relies on that exact number, but the visualisation, on the other
hand, does.
You’ll have to decompose the Post method to move forward.
7.2.2 Cohesion
How, or where, should you decompose the Post method in
listing 6.9?
Listing 7.2 The Post method using the new IsValid helper
method.
(Restaurant/f8d1210/Restaurant.RestApi/ReservationsContro
ller.cs)
Click here to view code image
var d = DateTime.Parse(dto.At!,
CultureInfo.InvariantCulture);
var reservations =
await
Repository.ReadReservations(d).ConfigureAwait(false);
int reservedSeats = reservations.Sum(r => r.Quantity);
if (10 < reservedSeats + dto.Quantity)
return new StatusCodeResult(
StatusCodes.Status500InternalServerError);
var r =
new Reservation(d, dto.Email!, dto.Name ?? "",
dto.Quantity);
await Repository.Create(r).ConfigureAwait(false);
When you look at the code for IsValid you don’t have to
know anything about the surrounding context. The calling
code doesn’t influence the IsValid method, beyond passing
an argument to it. Both IsValid and Post fit in your brain.
The most evident problem is the code smell that the IsValid
method is static8.Ittakesa ReservationDto parameter, but uses
no instance members of the ReservationsController class.
That’s a case of the Feature Envy [34] code smell. As
Refactoring [34] suggests, try moving the method to the
object it seems ‘envious’ of.
8. It’s not always a problem that a method is static, but in object-oriented
design, it could be. It’s worthwhile paying attention to your use of static.
if (!dto.IsValid)
return new BadRequestResult();
All tests pass. Don’t forget to commit the changes to Git and
push them through your deployment pipeline [49].
Maybe
Reservation? r = dto.Validate();
if (r is null)
return new BadRequestResult();
Figure 7.9 Hex flower hinting that each chunk can hide
other complexity.
Figure 7.10 Hex flower for the Validate method shown in
listing 7.5.
When you navigate to those parts of the code base, you also
learn that each class, or each method, fits in your brain at
that level of abstraction. You can diagram the chunks of the
code in a ‘hex flower’, as you’ve repeatedly seen throughout
this chapter.
If you decide to do that, make sure that you count all the
involved objects. That includes local variables, method
arguments, and class fields.
You must actively prevent code from rotting. You can pay
attention by measuring various metrics, such as lines of
code, cyclomatic complexity, or just counting the variables.
8.1.1 Affordance
How do you understand the word interface? You might think
of it as a language keyword, as in listing 6.5. In the context
of APIs, we use it in a broader sense. An interface is an
affordance. It’s the set of methods, values, functions, and
objects you have at your disposal to interact with some
other code. With good encapsulation, the interface is the set
of operations that preserve the invariants of the objects
involved. In other words, the operations guarantee that the
object states are all valid.
When you encounter a chair for the first time, it’s clear from
the shape how you can use it. Office chairs come with extra
capabilities: you can adjust their height, and so on. With
some models, you can easily find the appropriate lever,
while with other models, this is harder. All the levers look
the same, and the one you thought would adjust the height
instead adjusts the seat angle.
8.1.2 Poka-Yoke
A common error is to design a Swiss Army knife. I’ve met
many developers who think that a good API is one that
enables as many activities as possible. Like a Swiss Army
knife, such an API may collect many capabilities in one
place, but none are as fit for their purpose as a specialised
tool (figure 8.3). At the end of this design road lies the God
Class2.
2. God Class [15] is an antipattern that describes classes with dozens of
members implemented in thousands of lines of code in a single file.
I’ve met more than one software developer who recall those
days with loathing; who are happy that they are now
programmers, literary analysis far behind them.
if (IsOutsideOfOpeningHours(candidate))
return false;
Not all comments are bad [61], but favour well-named
methods over comments.
Task<IReadOnlyCollection<Reservation>> ReadReservations(
DateTime dateTime);
Still, it’s not clear whether that method creates a new row in
the database, or it updates an existing one. It might even do
both. It’s also technically possible that it deletes a row,
although a better candidate signature for a delete operation
would be Task Xxx(Guid id).
Names are still helpful, but you don’t have to repeat what
the types already state. This gives you room to tell the
reader something he or she can’t divine from the types.
With a method structure like void Xxx(), the only way you
can communicate with the reader is by choosing a good
name. As you add types, you get more design options.
Consider a signature like void Xxx(Email x). It’s still not clear
exactly what’s being done to the Email argument, but some
side effect must be involved. What could it be?
When you write code that calls the Allocate method, all you
need to know is that if you supply it a collection of
reservations, you receive a collection of tables. As far as
you’re concerned, there’s no side effect that you can
observe.
Good method names are still part of the code base. You look
at those every day. They’re also a good way to
communicate your intent to the reader.
The logic in listing 7.6 also doesn’t take into account the
time of day of the reservation. The implication is that there’s
only a single seating per day.
8.2.1 Maître D’
Only two lines of code in listing 7.6 handle the business
logic. These two lines of code are repeated in listing 8.5 for
clarity.
Listing 8.5 The only two lines of code from listing 7.6 that
actually make a business decision.
(Restaurant/a0c39e2/Restaurant.RestApi/ReservationsContr
oller.cs)
Click here to view code image
Listings 8.6 and 8.7 only show the publicly visible API. I’ve
hidden the implementation code from you. This is the point
of encapsulation. You should be able to interact with MaitreD
objects without knowing implementation details. Can you?
public MaitreD(
TimeOfDay opensAt,
TimeOfDay lastSeating,
TimeSpan seatingDuration,
IEnumerable<Table> tables)
Can you figure out what to do here? What should you put in
place of opensAt? A TimeOfDay value is required. This is a
custom type created for the purpose, but I hope that I made
a good job naming it. If you wonder how to create instances
of TimeOfDay, you can look at its public API. The lastSeating
parameter works the same way.
You can put the WillAccept method in listing 8.7 through the
same kind of analysis. If I’ve done my job well, it should be
clear how to interact with it. If you give it the arguments it
requires, it’ll tell you whether it will accept the candidate
reservation.
Listing 8.8 You can replace the two lines of business logic
shown in listing 8.5 with a single call to WillAccept.
(Restaurant/62f3a56/Restaurant.RestApi/ReservationsContro
ller.cs)
Click here to view code image
public ReservationsController(
IReservationsRepository repository,
MaitreD maitreD)
{
Repository = repository;
MaitreD = maitreD;
}
This looks like Constructor Injection [25], apart from the fact
that MaitreD isn’t a polymorphic dependency. Why did I
decide to do it that way? Is it a good idea to take a formal
dependency on MaitreD? Isn’t it just an implementation
detail?
public ReservationsController(
IReservationsRepository repository,
TimeOfDay opensAt,
TimeOfDay lastSeating,
TimeSpan seatingDuration,
IEnumerable<Table> tables)
{
Repository = repository;
MaitreD =
new MaitreD(opensAt, lastSeating, seatingDuration,
tables);
}
You’ve never seen the Seating class before. You don’t know
what the Fits method does. Still, hopefully you can get a
sense of where to look next, depending on your motivation
for looking at the code. If you need to change the way the
method allocates tables, where would you look? If there’s a
bug in the seating overlap detection, where do you go next?
It’s not even close to the limits of our brains’ capacity, but it
still abstracts two chunks (Seats and quantity) into one. It
represents yet another zoom-in operation in the fractal
architecture. When you read the source code of Fits, you
only need to keep track of Seats and quantity. You don’t have
to care about the code that calls the Fits method in order to
understand how it works. It fits in your brain.
8.3 Conclusion
Write code for readers. As Martin Fowler put it:
“Any fool can write code that a computer can understand. Good programmers
write code that humans can understand.” [34]
Obviously, the code must result in working software, but
that’s the low bar. That’s what Fowler means by ‘code that a
computer can understand.’ It’s not a sufficiently high bar.
For code to be sustainable, you must write it so that humans
understand it.
9.1 Git
Most software development organisations now use Git
instead of other version control systems such as CVS or
Subversion. Despite its distributed nature, you typically use
it with a centralised service such as GitHub, Azure DevOps
Services, Stash, GitLab, etc.
These rules are based on how various Git features work. For
example, if you want to see a list of commits, you can use
git log --oneline:
Such a list shows the summary line from each commit, but
none of the rest of the commit message. Even if you don’t
use Git from the command line, someone else might.
Additionally, some graphical user interfaces that make Git
more friendly actually interoperate with the Git command-
line API. There’ll be less friction if you follow the 50/72 rule.
You don’t have to write more than the summary, and often,
if the commit is small and self-explanatory, that’s all I do.
Proper Prose
If, however, you’re both editing the same line of code at the
same time, you have a merge conflict. How do you avoid
that? In the same way that you do with optimistic locking.
You can’t guarantee that it’ll never happen, but you can
make it unlikely. The shorter the time you spend editing the
code, the less likely it is that someone else made a change
to the same part at the same time.
Manoeuvrability
It’s not only about being fast. It’s about being able to
change direction and accelerate. This is also useful in
software development.
The more small commits you have, the easier you can
change your mind.
Bus Factor
How many team members can be hit by a bus before
development halts?
I’ve seen teams who find this too informal. They add notes
about co-authors in commit messages or when they merge
the changes into master. This is entirely optional.
Pair programming alone does not guarantee you achieve collective code
ownership. You need to make sure that you also rotate people through
different pairs and areas of the code, to prevent knowledge silos.” [12]
I hope that, since you’ve made it so far into the book, you’re
convinced that productivity is unrelated to how fast
someone types on a keyboard.
It’s true that a code review can introduce latency into the
development process. It is, however, a mistake to believe
that development is more efficient if most bugs remain
undetected until much later.
That’s really it11. You can assume, I think, that the author
will be happy to maintain his or her own code. If you’re also
ready to maintain it, then you’re two persons, and you’re on
the way to collective code ownership.
11. To be fair, you shouldn’t forget an even more important and basic question:
Does the change address a valid concern? Sometimes, you misunderstand
your assignment. I’ve done that. We all do that. It’s worth keeping that
question in mind during a code review. It can be a cause to reject a change,
but I don’t consider it the prime focus of a code review.
When you wish to merge a branch into master, you can issue
a pull request. This represents an appeal for your changes
to be integrated with the master branch.
9.3 Conclusion
Every team member has a few strong skills. It’s only natural
that you gravitate towards the part of the code base that
best suits you. If everyone does that, it may engender a
sense of ownership. That’s fine as long as it remains weak
code ownership [30]. This is when a piece of code has a
‘natural’ owner or main developer, but everyone is allowed
to make changes to it.
• Bug-fixing
GET / HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json
{
"links": [
{
"rel": "urn:reservations",
"href": "http://localhost:53568/reservations"
}
]
}
enableCalendar = calendarFlag.Enabled;
}
builder.ConfigureServices(services =>
{
services.RemoveAll<IReservationsRepository>();
services.AddSingleton<IReservationsRepository>(
new FakeDatabase());
services.RemoveAll<CalendarFlag>();
services.AddSingleton(new CalendarFlag(true));
});
}
Additionally, when I wanted to perform some exploratory
testing by interacting with the new calendar feature in a
more ad hoc fashion, I could set the "EnableCalendar" flag to
true in my local configuration file, and the behaviour would
also light up.
GET / HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json
{
"links": [
{
"rel": "urn:reservations",
"href": "http://localhost:53568/reservations"
},
{
"rel": "urn:year",
"href": "http://localhost:53568/calendar/2020"
},
{
"rel": "urn:month",
"href": "http://localhost:53568/calendar/2020/10"
},
{
"rel": "urn:day",
"href": "http://localhost:53568/calendar/2020/10/20"
}
]
}
I did try to make the change easy, but failed to realise just
how hard it would be. It doesn’t have to be that hard,
though. Follow a simple rule of thumb:
Task<IReadOnlyCollection<Reservation>> ReadReservations(
DateTime dateTime);
When you add a new method like that, the code fails to
compile until you add it to all classes that implement the
interface. The restaurant reservation code base only has
two implementers: SqlReservationsRepository and FakeDatabase.
I added the implementation to both classes in the same
commit, but that’s all I had to do. Even with the SQL
implementation, that represents perhaps five to ten minutes
of work.
Task<IReadOnlyCollection<Reservation>> ReadReservations(
DateTime dateTime);
Task<IReadOnlyCollection<Reservation>> ReadReservations(
DateTime min, DateTime max);
Alternatively,Icouldalsohaveaddedthenew ReadReservations
overload to both SqlReservationsRepository and FakeDatabase,
but left them throwing a NotImplementedException. Then, in
following commits, I could have used test-driven
development to flush out the desired behaviour. At every
point during this process, I’d have a set of commits that I
could merge with master.
When the new method is firmly in place, you can edit the
call sites, one at a time. In this way, you can take as much
time as you need. You can merge with master at any time
during this process, even if that means deploying to
production. Listing 10.10 shows a code fragment that now
calls the new overload.
Task<IReadOnlyCollection<Reservation>> ReadReservations(
DateTime min, DateTime max);
The code that called this MakeEntry helper method was itself
a helper method that received an
IEnumerable<Occurrence<IEnumerable<Table>>> argument, and I
wanted to gradually migrate callers. I realised that I could
do that if I added the temporary conversion method in
listing 10.18. This method supports the conversion between
the old class and the new class. Once I completed the
Strangler migration I deleted it together with the class itself.
10.3 Versioning
Do yourself a favour: Read the Semantic Versioning
specification [83]. Yes, all of it. It takes less than fifteen
minutes. In short, it uses a major.minor.patch scheme. You
only increment the major version when you introduce
breaking changes; incrementing the minor version indicates
the introduction of a new feature, and a patch version
incrementation indicates a bug fix.
10.4 Conclusion
You work in existing code bases. As you add new features,
or enhance the ones already there, or fix bugs, you make
changes to existing code. Take care that
youdosoinsmallsteps.
Few code bases are bootstrapped with the practices covered in the
first part of the book. They have long methods, high degrees of
complexity, poor encapsulation, and little automated test coverage.
We call such code bases legacy code. There’s already a great book
about Working Effectively with Legacy Code [27], so I don’t intend to
repeat its lessons here.
You don’t, but some of the practices outlined earlier improves your
chances. When you use tests as a driver for your production code,
you’re entering into a sort of double-entry bookkeeping [63] where
the tests keep the production code in place, and the production code
provides feedback about the tests.
The more you edit test code, the less you can trust it. The backbone
of refactoring, however, is a test suite:
“to refactor, the essential precondition is [...] solid tests” [34]
In practice, you’re going to have to edit unit test code. You should
realise, however, that contrary to production code, there’s no safety
net. Modify tests carefully; move deliberately.
Clearly, adding an entirely new test class may be the most isolated
edit you can make, but you can also append new test methods to an
existing test class. Each test method is supposed to be independent
of all other test methods, so adding a new method shouldn’t affect
existing tests.
You can also append test cases to a parametrised test. If, for
example, you have the test cases shown in listing 11.1, you can add
another line of code, as shown in listing 11.2. That’s hardly
dangerous.
[Theory]
[InlineData(null, "[email protected]", "Jay Xerxes", 1)]
[InlineData("not a date", "[email protected]", "Wk Hd", 8)]
[InlineData("2023-11-30 20:01", null, "Thora", 19)]
public async Task PostInvalidReservation(
[Theory]
[InlineData(null, "[email protected]", "Jay Xerxes", 1)]
[InlineData("not a date", "[email protected]", "Wk Hd", 8)]
[InlineData("2023-11-30 20:01", null, "Thora", 19)]
[InlineData("2022-01-02 12:10", "[email protected]", "3 Beard", 0)]
public async Task PostInvalidReservation(
You can also add assertions to existing tests. Listing 11.3 shows a
single assertion in a unit test, while listing 11.4 shows the same test
after I added two more assertions.
Assert.Equal(
HttpStatusCode.InternalServerError,
response.StatusCode);
These two examples are taken from a test case that verifies what
happens if you try to overbook the restaurant. In listing 11.3, the
test only verifies that the HTTP response is 500 Internal Server Error2.
The two new assertions verify that the HTTP response includes a clue
to what might be wrong, such as the message No tables available.
2. Still a controversial design decision. See the footnote on page 116 for more details.
I often run into programmers who’ve learned that a test method may
only contain a single assertion; that having multiple assertions is
called Assertion Roulette. I find that too simplistic. You can view
appending new assertions as a strengthening of postconditions. With
the assertion in listing 11.3 any 500 Internal Server Error response
would pass the test. That would include a ‘real’ error, such as a
missing connection string. This could lead to false negatives, since a
general error could go unnoticed.
Other changes are more risky3. When you perform such changes in
your production code, a good test suite will alert you to any
problems. If you make such changes in your test code, there’s no
safety net.
3. Add Parameter, for example.
Likewise, if you edit the test code without changing the production
code, a mistake may manifest as a failing test. Again, there’s no
guarantee that this will happen. You could, for example, first use the
Extract Method to turn a set of assertions into a helper method. This
is in itself a ‘safe’ refactoring. Imagine, however, that you now go
look for other occurrences of that set of assertions and replace them
with a call to the new helper method. That isn’t as safe, because you
could make a mistake. Perhaps you replace a small variation of the
assertion set with a call to the helper method. If that variation,
however, implied a stronger set of postconditions, you’ve just
inadvertently weakened the tests.
While such mistakes are difficult to guard against, other mistakes will
be immediately apparent. If, instead of weakening postconditions,
you accidentally strengthen them too much, tests may fail. You may
then inspect the failing test cases and realise that you made a
mistake.
For this reason, when you need to refactor your test code, try to do it
without touching the production code.
You can think of this rule as jumping from production code to test
code and back to production code, as illustrated by figure 11.3.
Figure 11.3 Refactor test code apart from production code.
Commit each refactoring separately. It’s safer to refactor
production code, so you can refactor it more often than test code.
Other, safer changes, such as renaming a method, may touch
both test and production code; those kinds of changes are not
shown in this figure.
To unit test that the system sends an email under the right
circumstances, I added the Test Spy [66] shown in listing 11.6 to
keep an eye on indirect output [66].
Assert.Contains(expected, postOffice);
Since I’d added a new method to the IPostOffice interface, I also had
to implement that method in the SpyPostOffice class. Since both the
EmailReservationCreated and EmailReservationDeleted methods take a
Reservation argument, I could just Add the reservation to the Test Spy
[66] itself.
But as I started writing a unit test for the new behaviour, I realised
that while I could write an assertion like the one in listing 11.7, I
could only verify that the Test Spy [66] contained the expected
reservation. I couldn’t verify how it got there; whether the spy added
it via the EmailReservationCreated or the EmailReservationDeleted
method.
This is one of the many reasons that Git is such a game changer,
even for individual development. It’s an example of the
manoeuvrability that it offers. I simply stashed4 my changes and
independently edited the SpyPostOffice class. You can see the result
in listing 11.9.
4. git stash saves your dirty files in a ‘hidden’ commit and resets the repository to HEAD.
Once you’re done with whatever else you wanted to do, you can retrieve that commit with
git stash pop.
Once I had refactored the test code, I popped the stashed changes
and continued where I’d left off. Listing 11.10 shows the updated
SpyPostOffice.
While these changes also involved editing the test code, they were
safer because they were only additions. I didn’t have to refactor
existing test code.
Don’t trust a test that you haven’t seen fail. If you changed a test,
you can temporarily change the System Under Test to make the test
fail. Perhaps comment out some production code, or return a hard-
coded value. Then run the test you edited and verify that with the
temporary sabotage in place, the test fails.
11.3 Conclusion
Be careful editing unit test code; there’s no safety net.
Some changes are relatively safe. Adding new tests, new assertions,
or new test cases tends to be safe. Applying refactorings built into
your IDE also tends to be safe.
Other changes to test code are less safe, but may still be desirable.
Test code is code that you have to maintain. It’s as important that it
fits in your brain as production code does. Sometimes, then, you
should refactor test code to improve its internal structure.
You run into errors and problems all the time. Your code
doesn’t compile, the software doesn’t do what it’s supposed
to, it runs too slowly, et cetera.
12.1 Understanding
The best advice I can think of is this:
12.1.2 Simplify
Consider if removing some code can make a problem go
away.
This may occasionally be the case, but it’s more likely that
the problem is a manifestation of an underlying
implementation error. You’d be surprised how often you can
solve problems by simplifying the code.
I could go on.
12.2 Defects
I once started in a new job in a small software startup. I
soon asked my co-workers if they’d like to use test-driven
development. They hadn’t used it before, but they were
keen on learning new things. After I’d shown them the
ropes, they decided that they liked it.
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"id": "21b4fa1975064414bee402bbe09090ec",
"at": "2022-03-02T19:45:00.0000000",
"email": "Phil Anders",
"name": "[email protected]",
"quantity": 2
}
Given that you already know what the problem is, you can
probably guess that the Reservation constructor expects the
email argument before the name. Since both parameters are
declared as string, though, the compiler doesn’t complain if
you accidentally swap them. This is another example of
stringly typed code [3], which we should avoid7.
7. One way to avoid stringly typed code is to introduce Email and Name classes
that wrap their respective string values. This prevents some cases of
accidentally swapping these two arguments, but as it turned out when I did it,
it wasn’t entirely foolproof. You can consult the example code’s Git repository
if you’re interested in the details. The bottom line was that I felt that an
integration test was warranted.
Assert.Equal(expected, actual);
}
Listing 12.4 Build script running all tests. The Build.sln file
contains both unit and integration tests that use the
database. Compare with listing 4.2.
(Restaurant/645186b/build.sh)
Click here to view code image
#!/usr/bin/env bash
dotnet test Build.sln --configuration Release
The Build.sln file contains the production code, the unit test
code, as well as integration tests that use the database. I do
day-to-day work that doesn’t involve the database in
another Visual Studio solution called Restaurant.sln. That
solution only contains the production code and the unit
tests, so running all tests in that context is much faster.
I don’t want to go into too much detail about how the test in
listing 12.3 works, because it’s specific to how .NET
interacts with SQL Server. If you’re interested in the details,
they’re all available in the accompanying example code
base, but briefly, all the integration tests are adorned with a
[UseDatabase]attribute. This is a custom attribute that hooks
into the xUnit.net unit testing framework to run some code
before and after each test case. Thus, each test case is
surrounded with behaviour like this:
. Create a new database and run all DDL9 scripts against it.
9. Data Definition Language, typically a subset of SQL. See listing 4.18 for an
example.
[HttpPost]
public async Task<ActionResult> Post(ReservationDto dto)
{
if (dto is null)
throw new ArgumentNullException(nameof(dto));
await Repository.Create(r).ConfigureAwait(false);
await
PostOffice.EmailReservationCreated(r).ConfigureAwait(false);
return Reservation201Created(r);
}
[Fact]
public async Task NoOverbookingRace()
{
var start = DateTimeOffset.UtcNow;
var timeOut = TimeSpan.FromSeconds(30);
var i = 0;
while (DateTimeOffset.UtcNow - start < timeOut)
await PostTwoConcurrentLiminalReservations(
start.DateTime.AddDays(++i));
}
When I wrote this test, it only ran for a few seconds before
failing. That gives me some confidence that the 30-second
timeout is a sufficiently safe margin, but I admit that I’m
guessing; it’s another example of the art of software
engineering.
It turned out that the system had the same bug when
updating existing reservations (as opposed to creating new
ones), so I also wrote a similar test for that case.
There are various ways you can address a defect like the
one discussed here. You can reach for the Unit of Work [33]
design pattern. You can also deal with the issue at the
architectural level, by introducing a durable queue with a
single-threaded writer that consumes the messages from it.
In any case, you need to serialise the reads and the writes
involved in the operation.
await Repository.Create(r).ConfigureAwait(false);
await
PostOffice.EmailReservationCreated(r).ConfigureAwait(false);
scope.Complete();
12.3 Bisection
Some defects can be elusive. When I developed the
restaurant system I ran into one that took me most of a day
to understand. After wasting hours following several false
leads, I finally realised that I couldn’t crack the nut only by
staring long enough at the code. I had to use a method.
~/Restaurant ((56a7092...))
$ git bisect start
~/Restaurant ((56a7092...)|BISECTING)
~/Restaurant ((56a7092...)|BISECTING)
~/Restaurant ((3035c14...)|BISECTING)
~/Restaurant ((aa69259...)|BISECTING)
Again, Git estimates how many more steps are left and
checks out a new commit (aa69259).
~/Restaurant ((75f3c56...)|BISECTING)
$ git bisect good
Bisecting: 9 revisions left to test after this (roughly 3
steps)
[8f93562...] Extract WillAcceptUpdate helper method
~/Restaurant ((8f93562...)|BISECTING)
$ git bisect good
Bisecting: 4 revisions left to test after this (roughly 2
steps)
[1c6fae1...] Extract ConfigureClock helper method
~/Restaurant ((1c6fae1...)|BISECTING)
$ git bisect good
Bisecting: 2 revisions left to test after this (roughly 1 step)
[8e1f1ce] Compact code
~/Restaurant ((8e1f1ce...)|BISECTING)
$ git bisect good
Bisecting: 0 revisions left to test after this (roughly 1 step)
[2563131] Extract CreateTokenValidationParameters method
~/Restaurant ((2563131...)|BISECTING)
$ git bisect bad
Bisecting: 0 revisions left to test after this (roughly 0
steps)
[fa0caeb...] Move Configure method up
~/Restaurant ((fa0caeb...)|BISECTING)
$ git bisect good
2563131c2d06af8e48f1df2dccbf85e9fc8ddafc is the first bad
commit
commit 2563131c2d06af8e48f1df2dccbf85e9fc8ddafc
Author: Mark Seemann <[email protected]>
Date: Wed Sep 16 07:15:12 2020 +0200
Restaurant.RestApi/Startup.cs | 32 +++++++++++++++++++---------
----
1 file changed, 19 insertions(+), 13 deletions(-)
~/Restaurant ((fa0caeb...)|BISECTING)
12.4 Conclusion
There’s a significant degree of personal experience involved
in troubleshooting. I once worked in a team where a unit
test failed on one developer’s machine, while it passed on
another programmer’s laptop. The exact same test, the
same code, the same Git commit.
At this point, I got involved, took one look at the repro and
asked both developers what their ‘default culture’ was. In
.NET, the ‘default culture’ is an Ambient Context [25] that
knows about culture-specific formatting rules, sort order,
and so on.
13.1 Composition
Composition and decomposition are intricately connected.
Ultimately, the purpose of writing code is to develop
working software. You can’t arbitrarily tear things apart.
While decomposition is important, as figure 13.1 illustrates,
you must be able to recompose what you decomposed.
Reservation? r = dto.Validate();
if (r is null)
return new BadRequestResult();
var isAccepted =
await Manager.Check(r).ConfigureAwait(false);
if (!isAccepted)
return new StatusCodeResult(
StatusCodes.Status500InternalServerError);
Try doing the exercise of X’ing out the method name. If you
do, you’re left with Task<bool> Xxx(Reservation reservation),
which looks like an asynchronous predicate. This must be a
method that checks if something about a reservation is true
or false. But if you look at listing 13.1 in that light, the Post
method only uses the Boolean value to decide which HTTP
status code to return.
return availableTables;
}
This may not be quite as bad as side effects, but it’ll still tax
your brain. What happens if we institute an extra rule on top
of Command Query Separation? The rule that Queries must
be deterministic?
When you zoom out again, the entire function collapses into
its result. It’s the only thing your brain needs to keep track
of.
Push all of that to the edge of the system; your Main method,
your Controllers, your message handlers, et cetera. For
example, consider listing 13.6 as a superior alternative to
listing 13.1.
[HttpPost]
public async Task<ActionResult> Post(ReservationDto dto)
{
if (dto is null)
throw new ArgumentNullException(nameof(dto));
await Repository.Create(r).ConfigureAwait(false);
return Reservation201Created(r);
}
Once it has collected all the data, it calls the pure WillAccept
function. Only if WillAccept returns true does the Post method
allow the side effect to happen.
Logging
Performance monitoring
Auditing
Metering
Instrumentation
Caching
Fault tolerance
Security
You may not need all of these, but once you need one, that
particular concern tends to apply to many features.
13.2.1 Logging
Most of the items in the above list are variations on logging,
in the sense that they involve writing data to some sort of
log. Performance monitoring writes performance
measurements to a performance log, auditing writes audit
data to an audit trail, metering writes usage data to what
will eventually become an invoice, and instrumentation
writes debug information to a log.
13.2.2 Decorator
The Decorator design pattern is also sometimes called
Russian dolls after the traditional Russian matryoshka dolls
that nest inside of each other, shown in figure 13.5.
Like the dolls, polymorphic objects can also nest inside each
other. It’s a great way to add unrelated functionality to an
existing implementation. As an example, you’ll see how to
add logging to the database access interface in listing 13.7.
Task<IReadOnlyCollection<Reservation>> ReadReservations(
int restaurantId, DateTime min, DateTime max);
It’d be even better to log only what you need. Not too little,
not too much, but just the right amount of logging.
Obviously, we should call this Goldilogs.
int z = x + y;
It might make sense to log what x and y are, particularly if
these values are run-time values (e.g. entered by a user, the
result of a web service call, etc.). You might do something
like listing 13.13.
13.3 Conclusion
Separate unrelated concerns. Changes to the user interface
shouldn’t involve editing the database code, or vice versa.
Although my wife would probably tell you that I’m one of the
most disciplined people she knows, I, too, tend to
procrastinate. Having a rhythm to my day helps me
minimise wasting my time.
14.1.1 Time-Boxing
Work in time-boxed intervals, like 25 minutes. Then take a
five-minute break. You probably know this as the Pomodoro
technique, but it’s not. The Pomodoro technique is more
involved [18], and I find the extra activities irrelevant.
Even if you don’t feel stuck, taking a break could make you
realise that you’ve just wasted the last fifteen minutes. That
doesn’t sound nice, but I’d rather waste fifteen minutes than
three hours.
My daughter was ten years old at the time. I didn’t want her
lounging around at home, so I put together a curriculum.
One of the things I had her do was to follow an online touch-
typing tutorial one hour a day. When the conflict was over
she was touch-typing, and she’s been typing like that since.
When Covid-19 locked down the schools in 2020, my
thirteen-year-old son got the same assignment. He, too, now
touch-types. It takes a few weeks, one hour aday,tolearn.
Still, it’s not the typing speed, or lack thereof, which makes
hunt-and-peck typing inefficient. The problem is that when
you’re always searching the keyboard for the next key to hit,
you don’t notice what’s happening on the screen.
Modern IDEs come with many bells and whistles. They tell
you when you make mistakes. I’ve been touch-typing since
my single-digit years, but I’m not a particularly accurate
typist. I use the delete key quite a bit.
I’ve seen programmers who are so busy hunting for the next
key that they miss all the help that the IDE offers. Worse, if
they mistype, they don’t see the mistake until they try to
compile or run the code. When they finally look at the
screen, they’re baffled that something doesn’t work.
It’s also the type of problem that once you start noticing it,
it’s too late. There are other problems that fall in that
category.
The same goes for domain names. They expire after several
years. Make sure someone renews them.
Consider how the way you organise the work impacts the
code base.
15.1 Performance
I’ve noticed a common pattern when I introduce an idea that
someone doesn’t like. Sometimes, I can tell from their facial
expressions that they’re struggling to come up with a
counter-argument to ward off the anathema. A little while
goes by, and then it comes:
15.1.1 Legacy
For decades, computers were slow. They could calculate
faster than a person could, but compared to modern
computers, they were glacial. At the time the industry
started to organise itself into the academic discipline of
computer science, performance was a ubiquitous concern. If
you used inefficient algorithms it could make a program
unusable.
15.2 Security
Software security is like insurance. You don’t really want to
pay for it, but if you don’t, you’ll be sorry that you didn’t.
15.2.1 STRIDE
You can use STRIDE threat modelling [48] to identify
potential security issues. It’s a thought exercise or workshop
where you think of as many relevant threats to your system
as you can. To help you think of potential issues, you can
use the STRIDE acronym as a kind of checklist.
15.2.2 Spoofing
Is the system vulnerable to spoofing? Yes, when you make a
reservation, you can claim to be whoever you like. You can
just give Keanu Reeves as the name, and the system will
accept that. Is that a problem? It could be, but we’ll
probably have to ask restaurant owners if that would give
them problems.
15.2.3 Tampering
Is the system vulnerable to tampering? It has a table of
reservations in a SQL Server database. Could someone edit
this data without being authorised to do so?
The REST API itself enables you to edit your reservation via
PUT and DELETE HTTP requests. Just as you can make a new
reservation without authenticating yourself, you can edit
one if you have the resource address (i.e. the URL). Should
we be concerned?
await conn.OpenAsync().ConfigureAwait(false);
await cmd.ExecuteNonQueryAsync().ConfigureAwait(false);
}
15.2.4 Repudiation
Can users of the system deny that they performed an
action? Yes, even worse, users can make reservations and
subsequently never show up. This is a problem that plagues
not only restaurants, but also doctors, hairdressers, and
many other places where reservations are made.
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"name": "Nono",
"year": 2021,
"month": 2,
"day": 23,
"days": [{
"date": "2021-02-23",
"entries": [{
"time": "19:45:00",
"reservations": [{
"id": "2c7ace4bbee94553950afd60a86c530c",
"at": "2021-02-23T19:45:00.0000000",
"email": "[email protected]",
"name": "Ann Archie",
"quantity": 2
}]
}]
}]
}
You can even write integration tests like the one in listing
15.3 to verify correct behaviour.
[Theory]
[InlineData( 1, "Hipgnosta")]
[InlineData( 2112, "Nono")]
[InlineData( 90125, "The Vatican Cellar")]
public async Task GetScheduleWithoutRequiredRole(
int restaurantId,
string name)
{
using var api = new SelfHostedApi();
var token =
new JwtTokenGenerator(new[] { restaurantId }, "Foo",
"Bar")
.GenerateJwtToken();
var client = api.CreateClient().Authorize(token);
Assert.Equal(HttpStatusCode.Forbidden, actual.StatusCode);
}
[Theory]
[InlineData( 0)]
[InlineData(-1)]
public void QuantityMustBePositive(int invalidQuantity)
{
Assert.Throws<ArgumentOutOfRangeException>(
() => new Reservation(
Guid.NewGuid(),
new DateTime(2024, 8, 19, 11, 30, 0),
new Email ("[email protected]"),
new Name("Ann da Lucia"),
invalidQuantity));
}
[Property]
public void QuantityMustBePositive(NonNegativeInt i)
{
var invalidQuantity = -i?.Item ?? 0;
Assert.Throws<ArgumentOutOfRangeException>(
() => new Reservation(
Guid.NewGuid(),
new DateTime (2024, 8, 19, 11, 30, 0),
new Email("[email protected]"),
new Name("Ann da Lucia"),
invalidQuantity));
}
Once you realise that you can let a library like FsCheck
produce arbitrary test values, you may start to look at other
test data in a new light. How about that Guid.NewGuid() ?
Couldn’t you let FsCheck produce that value instead?
[Property]
public void QuantityMustBePositive(Guid id, NonNegativeInt i)
{
var invalidQuantity = -i?.Item ?? 0;
Assert.Throws<ArgumentOutOfRangeException>(
() => new Reservation(
id,
new DateTime(2024, 8, 19, 11, 30, 0),
new Email("[email protected]"),
new Name("Ann da Lucia"),
invalidQuantity));
}
In fact, none of the hard-coded values have any impact on
the outcome on the test. Instead of "[email protected]", you
could use any string for the email. Instead of "Ann da Lucia",
you could use any string for the name. FsCheck will happily
produce such values for you, as shown in listing 15.7.
[Property]
public void QuantityMustBePositive(
Guid id,
DateTime at,
Email email,
Name name,
NonNegativeInt i)
{
var invalidQuantity = -i?.Item ?? 0;
Assert.Throws<ArgumentOutOfRangeException>(
() => new Reservation(id, at, email, name,
invalidQuantity));
}
Assert.Equal(
reservations.Select(r => r.At).Distinct().Count(),
actual.Count());
Assert.Equal(
actual.Select(ts => ts.At).OrderBy(d => d),
actual.Select(ts => ts.At));
Assert.All(actual, ts => AssertTables(sut.Tables,
ts.Tables));
Assert.All(
actual,
ts => AssertRelevance(reservations,
sut.SeatingDuration, ts));
}
This is actually the ‘implementation’ of the test. It receives a
MaitreD argument andanarrayof reservations so that it can
invoke the Schedule method.
[Property]
public Property Schedule()
{
return Prop.ForAll(
(from rs in Gens.Reservations
from m in Gens.MaitreD(rs)
select (m, rs)).ToArbitrary(),
t => ScheduleImp(t.m, t.rs));
}
You can dive into such a change coupling map to see an ‘X-
ray’ of a single file [112]. Which methods cause the most
problems?
With the right tools, you can also produce maps of hotspots
in your code, as shown in figure 15.4. Such interactive
enclosure diagrams present each file as a circle. The size of
each circle indicates its size or complexity, while the colour
indicates change frequency. The more commits that contain
the file, the more intense the colour [112].
15.4 Conclusion
When you hear the term software engineering, you most
likely think of ‘classic’ practices and disciplines such as
performance and security engineering, formal code reviews,
complexity analysis, formal processes, and so on.
16.1 Navigation
You didn’t write the code, so how should you find your way
in it? That depends on your motivation for looking at it. If
you’re a maintenance programmer and you’ve been asked
to fix a defect with an attached stack trace, you may
immediately go to the top frame in the trace.
Listing 16.1 shows the Main method that meets you in the
code base. It hasn’t changed since listing 2.4.
app.UseAuthentication();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
}
services
.AddControllers(opts =>
{
opts.Filters.Add<LinksFilter>();
opts.Filters.Add(new
UrlIntegrityFilter(urlSigningKey));
})
.AddJsonOptions(opts =>
opts.JsonSerializerOptions.IgnoreNullValues =
true);
ConfigureUrSigning(services, urlSigningKey);
ConfigureAuthorization(services);
ConfigureRepository(services);
ConfigureRestaurants(services);
ConfigureClock(services);
ConfigurePostOffice(services);
}
Few people like my answer: Just put all files in one directory.
Be wary of creating subdirectories just for the sake of
‘organising’ the code.
Your editor has tabs, and you can switch between them
using standard keyboard shortcuts1.
1. On Windows, that would be Ctrl + Tab.
The driver would then think about the name of that class, go
to the file view, scroll through it to find the file, and double-
click to open it.
All the while, the file was open in another tab. We worked
with it three minutes ago, and it was just a keyboard
shortcut away.
sp.GetService<ILogger<LoggingReservationsRepository>>();
var postOffice = sp.GetService<IPostOffice>();
return new EmailingReservationsRepository(
postOffice,
new LoggingReservationsRepository(
logger,
new SqlReservationsRepository(connStr)));
});
}
16.2 Architecture
I’ve had little to say about architecture. It’s not that I
consider it unimportant, but again, good books already exist
on the topic. Most of the practices I’ve presented work with
a variety of architectures: layered [33]; monolithic; ports
and adapters [19]; vertical slices; the actor model; micro-
services; functional core, imperative shell [11]; et cetera.
16.2.1 Monolith
If you’ve looked at the book’s sample code base, you may
have noticed that it looks disconcertingly monolithic. If you
consider the full code base that includes the integration
tests, as figure 16.4 illustrates, there are all of three
packages2. Of those, only one is production code.
2. In Visual Studio these are called projects.
You also can’t reuse parts of the code in new ways. What if
we wanted to reuse the Domain Model to run a scheduled
batch job? If you tried to do that, you would find that the
HTTP-specific code would tag along, as would the email
functionality.
That, however, is only an artefact of how I chose to package
the code. One package is simpler than, say, four.
16.2.2 Cycles
Monoliths tend to have a bad reputation because they so
easily devolve into spaghetti code. A major reason is that
inside a single package, all code can easily3 call all other
code.
3. To be fair, in a language like C#, you can use the private access modifier to
prevent other classes from calling a method. That’s not much of a barrier to a
developer in a hurry: Just change the access modifier to internal and move on.
As figure 16.8 implies, you may also want to unit test each
package separately. Now, instead of three packages, you
have seven.
[Fact]
public async Task ReserveTableAtNono()
{
using var api = new SelfHostedApi();
var client = api.CreateClient();
var dto = Some.Reservation.ToDto();
dto.Quantity = 6;
var at = Some.Reservation.At;
await AssertRemainingCapacity(client, at, "Nono", 4);
await AssertRemainingCapacity(client, at, "Hipgnosta", 10);
}
You may introduce Test Utility Methods [66] like listings 16.7
or 16.8. It turns out that the GetRestaurant method in listing
16.8 serves as a general-purpose entry point for any
HttpClient that wants to interact with this particular REST
API. Since it’s a multi-tenant system, the first step for any
client is to navigate to the desired restaurant.
16.4 Conclusion
‘Real’ engineering is a mix of deterministic processes and
human judgment. If you need to build a bridge, you have
formulas to calculate load-bearing strength, but you still
need to involve people to deal with the myriad complexities
related to the task. What sort of traffic should the bridge
support? What is the desired throughput? What are the
temperature extremes? What is the underground like? Are
there environmental concerns?
If engineering was an entirely deterministic process, you
wouldn’t need people. All it would require would be
computers and industrial robots.
A.4 Bisection
When you’re struggling to understand the cause of a
problem, bisection can be a useful technique. Remove half
of your code and check if the problem is still present. Either
way, you know in which half you can find the cause.
Use Git
Automate the build
Turn on all error messages
The items on the top of the list are more important than the
items at the bottom. See subsection 8.1.7.
. Make all tests pass by doing the simplest thing that could
possibly work.
3. Consider the resulting code. Can it be improved? If so, do it,
but make sure that all tests still pass.
4. Repeat.
This doesn’t mean that you can’t refactor your test code at
all, but you should be careful when you do. Particularly,
don’t refactor both test and production code at the same
time.
A.23 Slice
Work in small increments. Each increment should improve a
running, working system. Start with a vertical slice, and add
functionality to it. Read more in chapter 4.
A.24 Strangler
Some refactorings are quickly done. Renaming a variable,
method, or class is built into most IDEs and is just a button
click away. Other changes require a few minutes, or perhaps
hours. As long as you can go from one consistent state of
the code base to another consistent state in less than half a
day, you may not need to do anything special.
When you detect that this may be the case, use the
Strangler process to implement the changes. Establish the
new way of doing things side-by-side with the old way, and
gradually migrate code from the old to the new way.
This can take hours, days, or even weeks, but during the
migration process, the system is always consistent and
integrable. When no code calls the original API, you can
delete it.
A.25 Threat-Model
Take deliberate security decisions.
Spoofing
Tampering
Repudiation
Information disclosure
Denial of service
Elevation of privilege
[76] O’Toole, Garson, The Future Has Arrived – It’s Just Not
Evenly Distributed Yet, online article on
https://quoteinvestigator.com/2012/01/24/future-has-
arrived, 2012.
.NET, 310
1.0, 26
analyser, 29, 30, 67
Boolean default, 208
default culture, 255
deprecation, 220
ecosystem, 26
entry point, 309, 310
Iterator, 165
lightweight transaction, 249
package manager, 282
SQL Server, 245
time resolution, 122
;,see semicolon
@Deprecated, see Deprecated annotation
[Fact], see attribute
[InlineData], see attribute
[Obsolete], see attribute
[Property], see attribute
[Theory], see attribute
#pragma, 67
80/24 rule, 134
A
A/B testing, 300
AAA, see Arrange Act Assert
abstraction, 69
bad, 144
definition, 65, 100, 142, 176, 261, 264
example, 69
good, 146, 147
level, 151
high, 51, 311, 312, 323
versus detail, 79, 320
Accelerate (book), 5, 14, 37, 50
acceleration, 185
Accept header, see HTTP
acceptance test, see test
acceptance-test-driven
development, 61
access modifier, 319
internal, 143, 319
private, 76, 206, 319
action
bias, 237
Controller, 260
impure, 273, 274
outcome, 178
activity, 13–15, 18
exhausting, 190
involuntary, 42
mandatory, 276
physical, 279
recurring, 282
regular, 283
scheduled, 284
actor model, 318
Acyclic Dependency Principle, 321
acyclic graph, 314
Adapter, see design pattern
addition (arithmetic), 273
address
email, see also email, 102, 120, 268, 296
resource, 294–296, 325
AddSingleton method, 81
administrator rights, 299
ADO.NET, 79, 295
affordance, 156, 157, 176
agency, 42
agenda
meeting, 280
personal, 32
agile process, see process
AI, see artificial intelligence
Airbus A380, 17
airplane, 16
bomber, 16
algorithm, 89, 279, 287–289
distributed system, 300
inefficient, 289
sort, 44, 45
Ambient Context, 256
analyser, see tool
Analysis Paralysis, 49
annotation, 220
anonymous type, 64
antipattern, 158
API, 66, 156, 219
acronym, 156
ADO.NET, 79
affordance, 156, 176
analyser, 26
capability
advertisement, 158
too many, 158
change, 66, 162
date and time, 122
dependency, 219
deprecated, 221
design, 103, 158, 160
encapsulation, 176
exercise, 162
goal, 165
good, 155, 158, 171
principles, 155, 168, 176
FsCheck, 304, 305
HTTP
invalid input, 93
tools, 82
versus REST, 66
Maybe, 146
memorisation, 113
object-oriented, 221
public, 169–171, 196
reasoning about, 166, 171
REST, 205
benefit, 326
client, 325
deployed, 252
feature flag, 209
links, 323
logging, 272
secure, 251
test, 324
user interface, 323
specialised, 164
statically typed, 161, 167
stringly typed, 164
unfamiliar, 161, 185
xUnit.net, 90
APL, 134
application
line-of-business, 4
Application Programming Interface, see API
application/json, 62, 63
apprentice, see craft
architect, xxv, 3, 5, 314
architecture, 287, 288, 299, 300
component-based, 322
conventional, 51
Conway’s law, 285
CQRS, 166, 299
diagram, 51
fractal, 151, 152, 154, 174
big picture, 305
example, 175, 265, 312, 314, 325
functional core,
imperative shell, 273, 319
impact, 318
lack of, 37, 285
large-scale, 211
layered, 51, 52, 318
monolith, 318
one-size-fits-all, 318
poka-yoke, 321
ports and adapters, 319, 323
resilient, 299
Tradeoff Analysis Method, xxv
variety, 318
argument, see also parameter, 93, 242
additional, 55
as context, 265
counting, 153
generated, 301
logging, 272
mutation, 164
name, 93
passing, 142
replacing constant, 88
required, 156, 170, 171
string, 57
swap, 242, 268
valid, xxvii
armed guard, 292
army
air corps, 16
buyer, 16
Arrange Act Assert
as scientific method, 97
definition, 56
degenerate, 57
purpose, 57
structure, 69, 115
array, 304
JSON, 205
params, 170
replaced by container, 89
replacing scalar, 89
sort, 44
art
enjoyment of, 10
more than science, 65
of programming, 10
of risk assessment, 127
of software engineering, 37, 220, 327
determinism, 125
discomfort, 292
example, 93, 248
experience, 99, 256
shift toward
methodology, 47
artefact, 153
always current, 168
code, 181
inspection, 159
mistake-proof, 159
of packaging, 319
preservation, 153
artificial intelligence, 38
artist, 3, 10, 299
comics, 10
ASP.NET
configuration, 81, 150, 173, 317
Decorator, 271
Dependency Injection, 76, 77, 207, 270
entry point, 22, 26
exception, 93, 268
familiarity, 150, 310
IConfiguration interface, 311
Main method, 310
Model View Controller, 63, 67, 151
options, 315
web project, 21
Assert.True, 55
assertion, 65, 72, 116
abstract, 65, 323
append, 62, 225–227, 234
collection, 72, 231, 232
elegant, 72
explicit, 117
fail, 63
library, 55
message, 67
multiple, 226
phase, 56, 69, 73, 91, 97
roulette, 62, 226
single, 226
superficial, 55
tautological, 97, 233
Assertion Message, see design pattern
Assertion Roulette, see design pattern
asset, 47
assumption, 52, 248
attacker, 293–296, 298, 299
attention, 42, 46, 307
to keyword, 142
to metric, 105, 130, 132, 154
to names, 161
to performance, 289
to quality, 129, 154
to test suite, 247
attribute, 59, 301, 308
custom, 245
Fact, 90, 93
InlineData, 90, 95, 103, 301, 302
Obsolete, 220
Property, 301
Theory, 90, 93
UseDatabase, 245
audit, 16, 267
trail, 16, 267, 296
authentication, 251
absence of, 37
as mitigation, 296
JSON Web Token, 297
packaged, 318
two-factor, 112
author, 195–198
co-, 190
code, 41
main, 308
package, 282
pull request, 195
authorisation, 251, 311, 314, 318
automation, 19, 22, 32, 81
backup, 284
bisection, 252
build, 17, 18, 22, 32
test, 54, 55
checklist, 29
code review, 28
database, 83
HTTP, 82
quality gate, 32
test, 20, 82
threshold, 131
tool, 24, 25, 29, 32
awareness, 42
of circumstances, 188
of code rot, 154
of quality, 132
Azure, see Microsoft Azure
Azure DevOps Services, 24, 178, 197
B
B-17, 16, 17
B-tree, 45
backup, 178, 284
backwards compatibility, 219
bacteria growth, 307
balance, 56, 57, 69, 292, 296
Barr, Adam, 11, 13
base class, see class
baseball, 33, 41, 43
Bash, 19, 22, 252
bat-and-ball problem, 33, 41, 43, 44, 53, 71
batch file, 22
batch job, 319
BDD, see behaviour-driven development
Beck, Kent, 4, 116, 139, 210, 257
human mind, 115
behaviour
addition, 203
change, 162, 174, 227
combined, 141
complex, 138
compose, 262
correct, 248, 297
desired, 119, 128, 213
draining of, 81
existing, 203
external, 98
hidden, 204, 208, 274
incomplete, 209
incorrect, 268
new, 204
non-deterministic, 246, 265
object, 107
of existing software, 54
proper, 73, 121
test, 69, 115, 208
add, 198
unlawful, 181
with side effect, 266
behaviour-driven development, 53
behavioural code analysis, 306–308
benign intent, 126
bicycle, 41, 42, 278
big O notation, 289
big picture, xxv, 305, 316
bill by the hour, 292
binary classification, 29
bisection, see also Git, 251, 252, 255, 256
blog post, xxiii, xxvii, 255
boiler plate, 310
bomber, see airplane
Boolean
default, 208
expression, 55
flag, 144, 206, 207
negation, 172
return value, 145, 171, 261, 263
value, 261
bootstrap, 55, 223
Bossavit, Laurent, 192
bottleneck, 281, 289
boundary, 50, 61
HTTP, 61
system, 68, 69
test, 69, 76, 83
value, 300, 301
Boy Scout Rule, 31
brain, 151, 239
capacity, 175
compared to computer, 38, 112
constraint, 45, 46, 99, 152
emulator, 136
evaluating formal statements, 41
fits in your
API, 165
application, 150
architecture, 151, 152
chunk, 262, 265
code, 45, 46, 114, 115, 135, 141, 196, 198, 234
composition, 259
essential quality, 100
example, 142, 147, 175
part, 151, 174, 318
software engineering, 46
jumping to conclusions, 43, 45, 97
keeping track, 15, 153, 265
reduction, 88
seven chunks, 111, 136
side effect, 264
long-term memory, 111
misled, 44, 72
motor functions, 42
short-term memory, 39, 133
source code for, 43
subconscious, 41, 42
tax, 264
trust, 91
working memory, 131
branch, 81, 118, 138, 147, 152
add, 134
instruction, 39, 133
logic, 81
on constant, 119
outcome, 138
render, 152
break, 194, 238, 239, 276–279, 285
compatibility, 219, 220
contract, 66
cycle, 321
existing implementation, 122
functionality, 216
breakage, 35
breaking change, 196, 219–221, 227, 282
brewer, 12
bribe, 292
bridge, 12, 13, 326
Brooklyn, 117
Brooks, Fred, 46
browser, 314, 323
budget, 244, 262
buffer overflow, 293, 298
bug, see also defect, 174, 228, 241, 247, 298
address, 241
despite best efforts, 201
discovery, 192, 193
fix, 35, 192, 204, 220, 282
struggle, 40
production, 8
regression, 227, 228
report, 180, 246
reproduction, 184, 243
tons of, 289
uncaught, 252
zero, 240
build, 22, 131
automated, 17, 18, 22, 32, 54
configuration, 25
pipeline, 246
release, 22
repeatability, 272
script, 22, 23, 32, 55, 245
step, 22
build quality in, 158, 240
building a house, 3–7
bus factor, 188, 308
business
decision, 169, 299
goal, xxv, 319
logic, 169, 171, 246, 257, 322
out of, 5, 36, 37
owner, 293
process, 100
rule, 118, 124, 138, 290
encapsulation, 70, 72
by the book, 31
C
C, 293
language family, xxiv, 28, 135
C++, xxiv, 36, 44, 293
C#
80x24 box, 135
8, 28, 31, 92, 146, 162
access modifier, 319
analyser, 25
branching and looping
keywords, 133
compiler, 92, 94, 104, 144, 220
Data Transfer Object, 70
framework guidelines, 143
inheritance, 315
language
high-level, 298
verbose, 21
like Java, xxvi
managed code, 293
object-orientation, 266
operator
null-coalescing, 104, 133
null-conditional, 302
overload
return-type, 217
previous versions, 146
property, 143, 301, 302
struct, 207
syntax, xxvi
sugar, 143
type system, 100
var keyword, xxvi
verbosity, 134
CA1822, 139
CA2007, 57
CA2234, 58
cache, 267, 271
read-through, 271
cadastral map, 290–292
calculation, 13, 33, 38, 273
CalendarFlag class, 207, 208
call site, see also caller, 106, 210, 212–214
caller, 142, 146, 167, 211
check return value, 147
direct, 150
interaction with object, 99, 108, 109, 156
migrate, 211, 215–219, 221
multiple, 106
responsibility, 108
Campidoglio, Enrico, 19
canary release, 300
capacity
brain, 175
memory
long-term, 111, 112
short-term, 136, 137, 141
working, 114, 131
of restaurant, 115–117, 246, 249
hard-coded, 125, 126, 168
remaining, 123
of team, 192
system, 293
car, 41, 42, 87
Carlsberg, 12
carpenter, 9
case keyword, 133
category theory, 264
Category Theory for Programmers (book), 264
CD, see Continuous Delivery
cent, 41
certificate, 284
X.509, 284
chain of command, 285
chair, 156, 239, 277
affordance, 156, 157
office, 157
change, see also breaking
change
code structure, 98, 113
safe, 227
concurrent, 183
coupling, 306, 307
documentation, 168, 181
easy, 210
frequency, 307
impending, 221
in place, 215
motivation, 53
perspective, 7, 277
rate, 139, 257
significant, 210
small, 35, 61, 96
state, 78, 164, 165
Characterisation Test, 54, 58
chat forum, 284
chatter, 285
checklist, 16–18, 41
automated, 29
Command Query Separation, 166
do-confirm, 18
engineering, 13, 37
more than, 32
new code, 17, 32, 54, 55, 57
outcome, 32, 178
read-do, 18
Red Green Refactor, 224
STRIDE, 292, 294
surgery, 17
take-off, 17
team, 282
warnings as errors, 25
chef de cuisine, 3, 169
children’s book, 38
chunk, 115, 141, 262
abstraction, 141, 148, 149, 152, 175, 265
code, 114, 151
hex flower, 312
pathways, 138, 152
short-term memory, 112, 141
slot, 136, 148, 149
Circuit Breaker, see design pattern
claim
role, 297, 298
class, 27
base, 122, 229, 230, 232, 315
concrete, 213
declaration, 27, 268, 270, 310, 311
delete, 221
Domain Model, 145
field, 108, 139, 153, 206
immutable, 265
instance, 139
Humble Object, 123
immutable, 72, 107, 173
inheritance, 315
instance, 75, 76
member, 143, 150
instance, 67, 139, 142
name, 163, 169, 316
nested, 76, 232
private, 76
sealed, 27
clean code, 160
Clean Code (book), 288
client, 49, 111, 284
API, 66
code, 156, 326
concurrent, 183, 184
external, 61
HTTP, 62, 92, 247, 325, 326
test, 324, 325
postcondition, 103
SDK, 326
cloud, 20, 21, 45, 295
co-author, 190
coaching, xxv, 10, 88, 191
code, see also production code
auto-generated, 25, 40, 54, 72, 153
bad, 259–261
block, 139
complexity, 152
decomposition, 154, 155
small, 134, 144, 257
calling, 108, 214
dead, 7
defensive, 28, 108, 109
deletion, 7, 26, 27, 132, 187, 238
ephemeral, 19
high-level, 305
high-quality, 153
humane, 46, 174
imperfect, see also imperfection, 91, 123
incomplete, 204
liability, 47
low-level, 154
malicious, 126
metric, see metric
minimal, 20
multithreaded, 250
network-facing, 293
obscure, 43
organisation, 43, 51, 52, 59
layer, 51
quality, see quality
read more than written, 39, 44, 160
readable, 40, 41, 135, 196, redundant, 98, 187
removal, 237, 251
reuse, 40, 319
self-documenting, 161, 170
shared, see code ownership
simplest possible, 52
transformation, 88, 89, 119
unfamiliar, 146
unmaintainable, xxiv
unsurprising, 310
code analysis
rule, 27, 28, 59, 66, 67, 75, 139
static, 32, 57, 58, 75
driver, 53, 81
false positive, 29, 60
like automated code review, 28
suppression, 67
turn on, 31
warnings as errors, 29
tool, 24
code base
example, see example
greenfield, 307
memorise, 111
table of contents, 314
unfamiliar, 323
Code Complete (book), 139, 288
code ownership, 187
collective, 187–190, 194, 195, 197, 199
weak, 199
code quality, see quality
code reading, 196, 197
code review, 28, 189, 190, 192–199, 285, 295, 308
big, 195
civilised, 197
initial, 193
on-the-go, 190
repeat, 197
suggestion, 197
code rot, see also decay, 7, 130, 154, 325
code smell, 25, 56, 62, 139, 142
Feature Envy, 143, 154
coffee, 239
machine, 239
cognitive constraint, 45, 99, 176
coherence, xxiv, 43, 186
cohesion, 139, 154
collapse, 265
colleague, 78, 195, 239
help, 82, 198
collective code ownership, see code ownership
combat aviation, 185
combinatorial explosion, 69
Command, 166, 262
Command Query Responsibility
Segregation, 166, 299
Command Query Separation, 166
composition, 262
determinism, 264
predictability, 264
side effect, 258
signature, 171
violation, 261
command-line interface, 21
command-line prompt, 19
command-line tool, 21
command-line utility, 51
command-line window, 19
comment, 23, 180
apology, 161
Arrange Act Assert, 56
commit, 187
good, 167
legitimate, 167
misleading, 160, 161, 196
not all bad, 161
pragma, 67
replace with named method, 161, 167
stale, 161, 167, 168
TODO, 67
versus clean code, 160
Common Lisp, 36
commons, 290
communal table, 138
communication, 179, 285
ad hoc, 285
arbitrary, 285
channel, 219
face to face, 284
structure, 285
written, 285
commute, 278
comparison, 123
string, 255
compartmentalisation, 114
compatibility, 219
backwards, 219
breaking, 219, 220
compiler
C#, 92, 220
error, 25, 28, 29, 210
leaning on, 208, 210
Roslyn, 26
warning, 24–30, 220
complexity, see also cyclomatic complexity, 46, 307
analysis, 153, 308
collapsed, 265
essential, 46
hidden, 148, 149
increase, 129
indicator, 132
limit, 138
measure, 130
of called methods, 141
prediction, 132, 134
structure, 151
compliance, 235
Composite, see design pattern
composition, 258, 259, 315
nested, 260, 262
object, 259
pure function, 264
sequential, 262–264, 266, 274
Composition Root, see design pattern
comprehensibility, xxvi, 136, 153
computational complexity theory, 289
computer, 7, 327
away from, 239, 277–279, 285
compared to brain, 38, 39, 45, 46
disconnected, 292
in front of, 278, 279
limits, 45
personal, 11
reboot, 236
computer science, 44, 45, 287, 289
education, 8
Vietnam, 6
concentration, 42
concurrency, 183, 184, 191, 247, 249
conference, 31
GOTO, 158
software engineering, 11
conference room, 191
confidence, 111, 224, 227, 248, 250
configuration
application, 173, 174
ASP.NET, 81, 173, 317
feature flag, 207, 208
file, 82, 206, 208, 315
network, 293
system, 151, 207
value, 172, 173
Configure method, 150, 311, 312
ConfigureAwait method, 27, 57–59
ConfigureServices method 83, 151, 173, 312, 313
convention, 311
deleted, 27
ConfigureWebHost method, 83
connection string, 81–83, 151, 317
missing, 226
consciousness, 42
consensus, 191, 197
lack of, 68, 73, 107
constant, 75, 88, 89, 119
constraint, 6, 99
construction, 12, 13
real-world, 5, 6, 13
constructor, 170, 172–174, 207, 215–217
argument, 74, 230
as Query, 262
auto-generated, 72
overload, 170
parameterless, 76
precondition, 144
side effect, 262
validity, 106
Constructor Injection, see design pattern
contemplation, 26, 190, 278
contention, 183
context, 40, 126, 160, 181
surrounding, 142, 325
Continuous Delivery, 5, 19, 20, 85, 306
Continuous Deployment, 204, 275, 282, 291
Continuous Integration, 131, 182, 184, 204
server, 20, 22, 24, 58, 182, 246
contract, 66
advertisement, 161
design by, 99, 103
encapsulation, 99
external, 61
guarantee, 103
object, 87, 108
regression, 61
signing, 87
convention, 67, 180, 311
naming, 26
Conway’s law, 285
cooperation, 12, 284
Copenhagen, 42, 278 GOTO conference, 158
copilot, 18
copy and paste, 306
correctness, 289
cost, 4, 21, 305
sunk, 195, 210, 240
counter seating, 117
coupling, 306, 320
change, 306, 307
team, 308
Covid-19, 281
CQRS, see Command Query Responsibility Segregation
CQS, see Command Query Separation
craft, 8–10
crash, 92, 174, 268, 293, 298
airplane, 16
run-time, 268
creativity, 13
crisps, 7
critical resource, 187
cross-cutting concern, 201, 267, 314, 315
Decorator, 267, 271, 274
list of, 267
cross-platform, 36
cruft, 37, 153
cruising speed, 201
crunch mode, 193
culture, 32
hustle, 32
of quality, 154
oral, 285, 290
cURL, 82
customer, 36
paying, 219
potential, 28
scare away, 296, 297
CVS, 18, 178
cycle, 320–322
life, 52
Red Green Refactor, 96, 97
release, 5
cyclomatic complexity, see metric
D
daily stand-up, 194, 275, 282
format, 275
Danish alphabet, 256
Danish teachers’ union, 280
dark room, 7
data
access, 74, 316, 320–322
component, 316
implementation, 314
interface, 321
package, 321, 322
export, 257
import, 257
persisted, 64
tampering, 293, 294
version control, 305
Data Definition Language, 245
data store, 50, 61, 271
data structure, 45, 70
Data Transfer Object, 69, 70, 315
configuration, 173
role, 70
validation, 140, 142
versus Domain Model, 72, 145
data type
built-in, 101
integer, 101
database
access, 268, 295, 318
backup, 78, 284
cache, 267
cloud, 295
column, 183
create, 245, 246
data structure, 45
design, 6
fake, 73
graph, 78
implementation, 151
in-memory, 73
language, 78
lock, 183
logging, 272
permissions, 299
query, 45, 123, 265, 289
read and write, 268
real, 73
referential transparency, 264
relational, 6, 78, 151, 166, 243
restore, 284
row, 183
delete, 164
row version, 183
schema, 6, 78, 79, 257
SDK, 282
secure, 295
set up, 83
state, 246
tampering, 294
tear down, 83, 245
test, 243, 245, 246
transaction, 250
update, 243
DateTime struct, 72, 145, 171, 211
daughter, 280
DDD, see domain-driven design
DDL, see Data Definition Language
DDoS, see denial of service
dead code, 7
deadline, 192, 193
deadlock, 58
Death Star commit, 186
Debug configuration, 25
debugging, 256
decay, see also code rot
gradual, 130, 131, 153
decomposition, 114, 115, 138
code block, 154, 155
relative to composition, 258, 274
separation of concerns, 274
Decorator, see design pattern
default culture, 255
Danish, 256
default value, 124, 208
defect, see also bug, 35, 88
address, 241, 243, 249
deal with later, 240
detect, 159
elusive, 250
expose, 228
finding, 192, 251–255
fix, 129
ideal number of, 240
in the wild, 240
introduction of, 252
platform, 298
prevention, 193
production, 127
reproduction, 127, 241, 246, 250
run-time, 28, 268
troubleshooting, 235
defensive code, see code
delegation, 65, 169, 174, 175
DELETE, see HTTP
delimiter, 135
demonstration flight, 16
denial of service, 293
distributed, 293, 298
dependency
change frequency, 283
composition, 207
external, 243
formal, 172
injected, 152, 260
isolation of, 68
management, 6
package, 321
polymorphic, 172
primitive, 207
replace with fake, 84
source control, 237
stability, 283
update, 282, 283
visible, 172
dependency analysis, 287, 300, 306
dependency cycle, 321
Dependency Injection
book, xxiii
Container, 237, 270
configure, 270
dispense with, 207
register, 76, 77, 151, 317
responsibility, 207
Singleton lifetime, 76, 173
Dependency Injection Principles, Practices, and Patterns
(book), 207
Dependency Inversion Principle, 79, 320
deployment
automation, 19
repeatability, 272
sign-off, 23
deployment pipeline, 49, 250
establishment, 20, 23, 85
issue, 20
Deprecated annotation, 220
deprecation, 220, 221
describing a program, 6
design
by contract, 99, 100, 103, 109
error, 158
design pattern, 224, 279, 300
Adapter, 315
Assertion Message, 67
Assertion Roulette, 62, 226
Circuit Breaker, 267, 271
Composite, 259
Composition Root, 322
Constructor Injection, 75, 172, 310
Decorator, 267–271, 274, 317
Humble Object, 80, 123, 242
Iterator, 165
Model View Controller, 63, 67, 151, 311
Null Object, 76
Repository, 74
Unit of Work, 249
Value Object, 72
Visitor, 160
Design Patterns (book), 259
design phase, 6
desktop application, 293
Deursen, Steven van, 207
developer, see also software
developer
as resource, 5
back-end, xxiv
in a hurry, 319
main, 199, 289
original, 284, 289
remote, 194
responsibility, 295
single, 192
Visual Studio, 30
development, see also software development
back-end, 9, 188
front-end, 9
greenfield, 2, 203
individual, 231
user-interface, 188
development environment, see also IDE, 135, 157, 168
development machine, 22, 82, 205, 255, 256
Devil’s Advocate, 119–121, 123–125, 128
DI, see Dependency Injection
diff, 179, 181
tool, 238
Dijkstra, Edsger, 11
diminishing returns, 191
directed graph, 227
directory, see also subdirectory, 19, 314, 315
discipline
academic, 288, 305
engineering, 10, 11, 13, 14, 31
future, 327
esoteric, 34
intellectually demanding, 33
discomfort, 292
discoverability, 158
discriminated union, see sum type
discussion
repeated, 285
technical, 284
written, 284
disillusionment, 10
disjoint set, 139
do keyword, 133
document database, see database
documentation, 59, 60, 160, 167
high-level, 168
online, 28
rule, 57, 58
scalability, 280
stale, 41, 168, 196
doing dishes, 278
dollar, 33
domain, 148
Domain Model, 70–72, 74, 315, 318–322
abstraction, 79
clean, 79
evolution, 100
motivation, 145, 169
domain name, 284
domain-driven design, 53, 169
domain-specific language, 78
Don’t repeat yourself, see DRY principle
done done, 192
door handle, 156
dot-driven development, 158
dotnet
build, 22, 55
test, 55
double-blind trial, 237
double-entry bookkeeping, 53, 91, 224
driver
code analysis, 75, 81
code as answer to, 88
example, 53
extrinsic, 53
multiple, 76
of behaviour, 68
of change, 53, 103
of implementation, 61
of transformation, 91
test, 74, 115, 224
integration, 208
driving, 41, 42
Dronning Alexandrine’s bridge, 12
DRY principle, 107
DSL, see domain-specific language
DTO, see Data Transfer Object
duplication
address, 147, 234
look out for, 52
needless, 197
of implementation code, 91
test code, 61
validation, 144
DVCS, see version control system
dyslexia, 181
E
economics, 8, 132
edge
of system, 265, 266
edge case, 69
editor, see also IDE, 13
vertical line, 135
education
computer science, 8, 44
self, 280
effort, 16
continual, 35
heoric, 210
little, 42
mental, 42, 43
small, 32
Eiffel (language), 100
elevated privileges, 236
elevation of privilege, 293, 299
email
address
as identification, 102
bogus, 102
validation, 102
confirmation, 229
grammar, 180
personally identifiable
information, 294, 296, 297
procrastination, 244, 277
unit test, 229, 230, 303
employee, 297
hire, 282
new, 113
regular, 194
empty string, 57, 103, 105
emulator, 39, 136
encapsulation
broken, 144
business rule, 70, 72
contract, 87, 99
Data Transfer Object, 70
good, 156
invariants, 144
misunderstood, 108
poor, 173, 223
purpose, 165, 169
state, 106
versus strings, 58
enclosure diagram, 307, 308
engineer, see also software
engineer, 3, 12, 13, 177
chemical, 12
real, 13, 177, 199
engineering, see also software engineering, 29, 33
deterministic process, 327
discipline, 10, 11, 13, 14, 31
future, 327
mechanical, 44
method, 13, 17
practice, 199, 210
real, 326
relationship with science, 44, 98
security, 300, 308
English, 181 US, 256
Entity Framework, 79
entry point, 52, 150, 309, 310 ASP.NET, 22, 26
environment
concern, 326
configuration, 82
data, 264
development, 135, 157, 168
pre-production, 20
production, 85, 127
debugging, 256
deployment, 23
lack of, 20
programming, 24, 25
Equals method, 72
equilibrium
unstable, 153
error
compile-time, 92, 160
finding, 255
pilot, 16
programmer, 243
report, 93
reproduction, 243
spelling, 25
error message, 17, 18
essay, 160
essence, 141, 146
ethics, 292
eureka, 278
exception, 301
ArgumentNullException, 92
ArgumentOutOfRange-Exception, 300, 301
handling, 92
message, 93, 107, 180
NotImplementedException, 213
NullReferenceException, 92
run-time, 92
versus compiler error, 160
type, 92
unhandled, 93, 127, 268
exclamation mark, 91, 92, 94, 96
execution
branch, 119
path, 88, 115
repeatability, 272
exercise, 284, 316
API design, 162, 163
physical, 278, 279
experience, 37, 125
accumulated, 9, 10
individual, 11, 13, 93, 99, 235, 256
personal, 255
professional, xxiv, 44
subjective, 42
experiment, 15, 97, 236, 237, 241
Git, 18, 185, 186
result, 11
F
F#, 134, 146, 160, 322, 323
Fact attribute, 90, 93
fail fast, 103
failure, 148, 210, 247
single point of, 187
Fake Object, see Test Double
FakeDatabase class, 69, 73, 74, 83, 122, 212, 213
fallacy
logical, 37
sunk cost, 195, 210, 240
false negative, 97, 226, 247, 248
false positive, 29, 60, 247
falsifiability, 97, 237
fault tolerance, 267, 271, 300
feature, 50, 52, 192, 193
add, 35, 40, 129
big, 220
completion, 193, 208
configuration, 208
cutting across, 267
delivery, 276
deployed, 201
difficult, 204
done, 192
end-to-end, 52
incomplete, 204
new, 204, 209, 220, 282
optional, 28
security, 251, 252, 271
subdirectory per, 314
suggestion, 278
feature branch, 220
Feature Envy, see code smell
feature flag, 184, 204, 206–209, 220
configuration, 208
fee, 28
reservation, 296
feedback, 49, 52, 60, 160
feudalism, 290
Fiddler, 82
file, 314
code, 315
dirty, 231
executable, 318
organisation, 314
filter, 122, 123, 262, 314, 315
finite state machine, 300
firefighting, 193, 275
Firefox, 314
first language, 181
fits in your head, 150–152, 154, 176, 262, 274, 312
API, 165
architecture, 314
chunk, 262, 265, 312
code, 114, 115, 135, 198, 309
composition, 259
criterion, 196
evaluation, 141, 311, 317, 323
example, 142, 147, 175
object, 100
part, 174, 318
system, 114
flag, see also feature flag, 144, 206, 208
flow, see also zone, 42, 277
focus, 16, 244
Foote, Brian, 153
for keyword, 133
foreach keyword, 133
forensics, 40
forgetfulness, 16, 38
formatting, 187, 196, 198
blank line, 56
culture, 256
Git commit, 179, 180
guard, 24
line width, 136
foundation, 6, 11, 19
Fowler, Martin
code that humans can understand, 45, 176
Data Transfer Object, 70
quality, 35, 37, 40, 47
Strangler, 211
FP, see functional programming
fractal architecture, see architecture
fractal tree, 151, 152
fractals, 151, 154
framework
automated testing, 14
data-access, 52
experience with, 311
familiarity, 309, 310
MVC, 63, 67
not-invented-here syndrome, 6, 36
security, 271
unit testing, 55, 90, 305
Freakonomics (book), 132
Freeman, Steve, 7
frequency, 283
change, 307
frog
boil, 130
fruit
low-hanging, 24
FsCheck, 301–305
NegativeInt, 302
NonNegativeInt, 302, 304
PositiveInt, 302
function
self-contained, 46
functional core, imperative shell, 266, 273, 318
example, 319
in presence of
object-oriented code, 274
functional programming, 14, 238, 264, 266
influence on C#, xxvi
FxCop, 26
G
Gabriel, Richard P., 36
game programming, 9
Gantt chart, 6
gardening, 3, 7, 8
Gawande, Atul, 16, 17
generics, 146, 215
nested, 216
geographical survey, 127, 128
geometry, 127
GET, see HTTP
GetHashCode method, 72
getter, 108, 143
GetUninitializedObject method, 107
Gibson, William, 14, 327
Git, 14
.git directory, 19
50/72 rule, 179, 180
Bash, 19, 252
basics, 18
bisect, 251–255
blame, 40
branch, 184–187, 197
command line, 19, 179, 180
command-line interface, 18
commit
big, 186
empty, 19
five minutes from, 218
hidden, 231
ID, 253
self-explanatory, 180, 181
small, 185
commit message, 167, 168, 178–182, 198
co-author, 190
empty, 178
connection string, 82
database schema, 79
de-facto standard, 18, 178
experimentation, 185
game changer, 231
graphical user interface, 18, 19, 180
HEAD, 231
history, 59, 186, 187
init, 19
issues, 18
learning, 18
log, 179, 283
master, 184, 185, 187, 197
deployment, 23
incomplete feature, 204
merge, 197, 198, 213, 216, 218
online service, 19, 197
push, 186
reason for using, 182
rebase, 1
repository, 19, 255
local, 184
secrets, 82
stage, 233
stash, 186, 231, 232, 241
tactics, 178
user-friendliness, 18
Git flow, 197
GitHub, 19, 178, 197, 284
GitHub flow, 197, 198
GitLab, 178
glucose, 43
Go To Definition, 315, 317
Go To Implementation, 317
God Class, 158
Goldilogs, 272
GOOS, see Growing Object-Oriented Software, Guided by
Tests (book)
GOTO conference, 158
government, 35
grammar, 180
graph
acyclic, 314
directed, 227
graph database, 78
graphical user interface, 18, 19, 82, 164, 180
greater than, 123, 255, 256
greater than or equal, 123, 302
grocery store, 279
ground level, 34
Growing Object-Oriented Software, Guided by Tests (book),
7, 61, 78, 325
growing season, 290
guarantee, 87, 103, 108
guard
armed, 292
Guard Clause, 100, 139, 145, 187, 262
natural numbers, 101, 102
null, 75, 94
GUI, see graphical user interface
GUID, 264, 265, 294
guidance, 11, 21
guideline, 10, 26, 88, 89, 103
guild, see craft
guitar, 10
H
hack, 32, 92, 131, 321
hammer, 291
happy path, 64
hard drive, 19, 112, 186
hard limit, 138
hard-coded
capacity, 125, 126, 168
constant, 89
path, 66
return value, 61
value, 75, 77, 78, 83, 87, 88, 303
hardware, 14, 21, 290
control of, 126
hash, 183
hash index, 45
Haskell, 160, 274
absence of null references, 146
big function, 134
category theory, 264
learning, 266
linter, 25
Maybe, 146
QuickCheck, 301
side effect, 166
HDMI, 159
head waiter, see maître d’hôtel
headline, 180
height restriction barrier, 159
hello world, 54, 55, 61
helper method
extract, 139, 234
motivation, 66
Henney, Kevlin, 6
heroism, 210
heuristic, 10
API design, 155
Arrange Act Assert, 56, 69, 115
for first feature, 64
hex flower, 137, 138, 141, 142, 148–151, 312, 313, 317
hexagon, 137, 138, 142, 150
Hickey, Rich, 46, 238
hierarchy, 314
directory, 315
inheritance, 315
of communication, 167, 179, 219
rigid, 285
type, 227
hill, 34
hipster, 117, 138, 168
history, 12
line width, 135
of software development, 14, 15
rewrite, 1, 19
HIV, 29
Hoare, Tony, 11
HomeController class, 63, 207
hotspot, 307, 308
house, 3, 5, 87
House, Cory, 196
HTTP, 318, 322, 326
200 OK, 55
201 Created, 55
204 No Content, 115
400 Bad Request, 93
403 Forbidden, 297, 298
500 Internal Server Error, 93, 94, 116, 226
boundary, 61
client, 247
code, 319
content negotiation, 62
DELETE, 294, 296
GET, 205, 251, 293, 296, 297, 323
header, 325
Accept, 62
Content-Type, 62, 63
Location, 295
interaction, 205, 209
POST, 78, 82, 294, 323, 325
PUT, 242, 294
request, 21, 67, 151, 312
logging, 272
response, 226, 297
content, 226
logging, 272
specification, 116
status code, 55, 65, 94, 116, 261
error, 115
verb, 66
HttpClient class, 324, 325
HTTPS, 295, 296
humane bounds, 154
humane code, 46, 174
Humble Object, see design pattern
hunt-and-peck typing, 281
hypermedia controls, 66, 205
hypothesis, 37, 97, 236, 237, 241
I
IConfiguration interface, 82, 311
IDE, 14
acronym, 281
file view, 316
guidance, 21, 170, 281
navigation, 310, 315
refactoring, 223, 227, 234
use to compile, 22
if keyword, 133
illegal states
unrepresentable, 159
illusion
maintainability, 26
immutability
class, 72, 107, 173
field, 265
object, 106
imperative mood, 18, 179, 180
imperfection, 91, 106, 123, 126
implementation detail, 172, 264
coupling, 107, 320
Dependency Inversion Principle, 79
hidden, 165, 170
irrelevant, 176
unknown, 99, 171, 174
view, 316
improvement
heuristic, 119
loss of ability, 35
impure action, 273, 274
incantation, 236
incentive
perverse, 132, 292
indentation, xxv, 271
infinity, 152
information disclosure, 293, 296
infrastructure
cloud, 45
code, 204
digital, 35
inheritance, 315
single, 315
initialisation, 106
object, 144
InlineData attribute, see attribute
inlining, 290
input, 50, 103
acceptable, 103
invalid, 93, 95, 100, 103
logging, 273
malevolent, 293
null, 99
parsing, 147, 148
query, 50
required, 156
valid, 100
validation, 77, 92, 115
insight, 210, 239, 268, 278
inspiration, 17, 44, 279
instruction, 17, 160
instrumentation, 267, 272
insurance, 292
intangible, 13, 44, 291, 292
integer, see also number, 100
16-bit, 101
8-bit, 101
default value, 124
non-negative, 302
non-positive, 301, 302
signed, 102
unsigned, 102
Integrated Development Environment, see IDE
integration test, see test
IntelliSense, 157
intent, 163, 167, 196, 198
benign, 126
code, 161
interaction
external world, 145, 229
hidden, 262
HTTP, 205, 209
IDE, 281
interpersonal, 197
object, 99, 103, 108
social, 177
Interactive Development Environment, 281
interception, 269, 295
interface
add member, 122, 213
affordance, 156
cycle, 320
delete member, 214
extra method, 212
go to implementation, 315
versus base class, 229
internal, see access modifier
Internet, 14
internet
disconnected from, 292
interpreter, 180
introvert, 190
intuition, 33, 38, 41
invariant, 80, 109, 144, 145, 156
investigation, 16, 241, 307
IPostOffice interface, 230, 231, 233, 271, 317
IReservationsRepository interface, 73, 74, 76, 77, 79, 81, 83,
121, 123, 156, 161–163, 211–214, 269, 271, 316, 317
IRestaurantManager interface, 260, 261
IT professional, 293, 295, 298
Iterator, see design pattern
J
Java
deprecation, 220
developer, xxiv
example code, xxiv, xxvi
high-level language, 298
inheritance, 315
like C#, xxvi
managed code, 293
null, 146
JavaScript, 25, 282, 298
Jenkins, 24
job security, 113
journeyman, 9, 10
JSON, 250
array, 205
configuration, 315
document, 61, 70, 83, 92, 173
object, 64
parsing, 326
representation, 205, 323, 325
response, 61, 62
serialisation, 64, 325
JSON Web Token, 251, 252, 282, 297, 298
redaction, 272
judgment, 37, 220
human, 326
moral, 31, 32
subjective, 53, 79
jumping to conclusions, 43, 45, 97
JWT, see JSON Web Token
K
Kahneman, Daniel, 42, 43
Kanban board, 275
kata, 280
Kay, Alan, 11, 12
keyboard, 191, 281
keyboard shortcut, 315, 316
king, 290, 291
King, Alexis, 147
KISS, 238
kitchen, 117, 118, 168, 169
kitchen timer, 277
knowledge
existing, xxiii, xxiv, 11
expansion, 280
local, 290
loss of, 285
packaged, 26, 45
painstakingly acquired, 114
knowledge distribution, 308
knowledge gap, 288
knowledge map, 308
knowledge silo, 190, 194
knowledge transfer, 191
Knuth, Donald, 11
L
lab coat, 237
lambda expression, 270
land, 87, 290
ownership, 290
language, see also programming language
familiarity, 309
first, 181
latency, 190, 192
laterisnever, 240
LaTeX, xxviii, 4
law of unintended consequences, 132
layer, 52, 320
layered architecture, see architecture
leader
technical, 132
leadership, 300
lean manufacturing, 159
lean on the compiler, 210
lean software development, 158, 240
Lean Startup (book), 50
left to right, 122
legacy code, 111, 223
avoid, 114
deliberate, 129
escape, 114
gradual decay, 153
memory, 113, 136
programmer, 113
realisation, 129
refactoring, 114
legacy system, 211
legibility, 290–292
less than, 123, 255
less than or equal, 123
liability, 87
code, 47
library
JSON Web Token, 282
mock object, 237
open-source, 219
reusable, 45, 58
life cycle, 52
light, 7, 9, 210
line
blank, 56
Arrange Act Assert, 56, 57, 69, 115
Git commit message, 179
section, 139
vertical, 135
wide, 271
line break, 23, 271
line width, xxvii, 135
line-of-business application, 4
lines of code, see metric
LINQ, 122, 124, 125
linter, 24, 25, 30, 32
as driver, 53, 76
false positive, 29
warnings as errors, 29
Liskov Substitution Principle, 227
listen to your tests, 325
literal, 59
literary analysis, 160
localhost, 205
Location header, see HTTP
locking
optimistic, 183, 184
pessimistic, 183
log, 93, 267, 268
log entry, 174, 270, 271
logging, 77, 267, 268, 270–273, 315, 317, 318
LoggingPostOffice class, 271
LoggingReservations Repository class, 270, 271
logistics, 5, 13
long hours, 193, 279
loop, 133
tight, 289
lottery factor, 188
low-hanging fruit, 24
LSM-tree, 45
Lucid, 36, 40
lunch, 194, 282
M
maître d’hôtel, 102, 169, 246
authentication, 297
schedule, 251, 293, 296, 304
machine code, 290
machine learning, 9
magic spell, 236
Main method, 265, 309, 310
maintainability
illusion, 26
maintainer, 189
maintenance burden, 214
maintenance mode, 4
maintenance task, 221
maintenance tax, 212
MaitreD class, 169–174, 304
man-in-the-middle attack, 293, 295, 296
management, 191, 194
manager, 31, 177, 178, 210, 292
non-technical, 31, 292
manoeuvrability, 185, 231, 233
manufacturing, 327
lean, 159
Martin, Robert C.
abstraction, 65, 100, 142, 176, 261, 264
Transformation Priority Premise, 88, 89
triangulation, 119, 123
mason, 5
master, see craft
materialised view, 299
mathematics, 33, 41, 264
fractals, 151, 152
matryoshka dolls, 268, 269
Maybe, 146, 162
measure, 37, 97, 246, 290
triangulation, 127
measurement, 291, 292
performance, 267
proxy, 292
triangulation, 127, 128
medieval village, 290
meeting, 235, 275, 280
memorisation, 111–114
memory
aid, 17
fading, 112
long-term, 111–113, 136, 141
short-term, 111–113, 154
capacity, 136, 137, 141
chunk, 141
hexagonal layout, 142
limit, 39, 46, 99, 133
magical number seven, 39
slot, 136, 138, 149
unreliable, 38, 39
working, 39, 111, 114, 131
memory footprint, 289
merge conflict, 183
merge hell, 182–184, 220
merge sort, 45
metaphor, 3–8, 38, 97
accountant, 8
author, 8
brain, 38, 112
gardening, 7, 8
house, 4–8
Russian matryoshka dolls, 269
software craftsmanship, 9
triangulation, 127
metering, 267
method
signature, 164
method call
blocking, 102
methodology, xxv, 15, 256
deliberate, 56
engineering, 13
lack of, 10
quantitative, 327
scientific, 97
software development, 47, 53
software engineering, 50
metric
attention, 130, 154
cyclomatic complexity, 130–133, 147, 149, 150, 306
example, 136, 138–140, 174, 260, 262, 311, 312, 317,
323
explicitly consider, 152
of called methods, 140
one, 242
seven, 46, 105, 169
threshold, 136
Visual Studio, 105
depth of inheritance, 105
invent, 132
lines of code, 132, 134
attention, 154
example, 139, 140, 174, 260, 312, 317, 323
explicitly consider, 152
Visual Studio, 105
monitor, 130
practicality of, 132
useful, 132
Visual Studio, 133
Meyer, Bertrand, 100, 166
micro-commit, 187
micro-service, 318
microseconds, 289
Microsoft, 28, 72, 78, 293
Microsoft Azure, 268
Milewski, Bartosz, 264
milliseconds, 289
mindset
engineering, 14
team, 132
tinkering, 36
minimal working example, 251, 255, 256
mistake
all the time, 41
cheap, 185
commit, 185
easy to make, 53, 224
hide, 186, 201
prevention, 126
proof, 158
reduce risk of, 88
repeat, 243
typing, 281
misuse, 158
mitigation, 292, 295, 297, 298
mob programming, 184, 191, 192, 199, 316
driver, 316
mobile phone app, 293
mock, 73, 107
Model View Controller, see design pattern
module, 189, 258, 314
money, 12, 21, 35
monolith, 219, 318, 319, 323
morals, 31, 32
morning, 194, 275, 280
motivation, 53, 57, 309
Domain Model, 145
extrinsic, 53
intrinsic, 38
package, 321
process, 178
rule, 28
motor function, 42
multi-tenancy, 269, 323, 325
mutation, 106
artefact, 153
MVC, see Model View Controller
myopia, 37, 192, 255
N
naming convention, 26
nanosecond, 122, 289
NASA, 11
NATO, 11
natural number, see number
navigation, 24, 111, 151, 180, 314
need it later, 215
negative number, see number
nested class, see class
nesting, 259, 260, 262, 274
dolls, 268, 269
object, 268
nihilism, 10
nil, 89
no-op, 67
Nobel laureate, 42
noble, 290, 291
non-breaking change, 219
non-determinism, 265, 266, 273
non-nullable reference type, see null
Norman, Donald A., 156, 157
NoSQL, 78
notification area, 277
NPM, 282
NuGet, 282
null, 28
ArgumentNullException, 92
check, 92, 99
coalescing operator, 104, 133
Guard Clause, 75, 81, 94, 96, 98
nil, 89
non-nullable reference type, 28, 99, 106, 144, 162
null-forgiving operator, 144
nullable reference type, 28, 72, 92, 146, 162
alternatives to, 146
gradually enabling, 31
suppression, 144
NullReferenceException, 92
return value, 161
Null Object, see design pattern
NullRepository class, 76, 77, 81
number, see also integer 128-bit, 294
increment, 133
natural, 100–102, 106, 300
negative, 102, 107, 300, 301
one, 133
positive, 102, 108
random, 273
seven, 39, 46, 111, 131, 133, 138
ten, 41, 43
zero, 107
number-line order, 122
NUnit, 301
O
object
composition, 259, 315
equality, 72
immutable, 106
polymorphic, 268
shared, 76
object-oriented API, 221
object-oriented code, 238, 266, 274
object-oriented
composition, 258, 259
object-oriented
decomposition, 274
object-oriented design, 139, 142, 160, 259, 274
object-oriented language, xxiv, 146, 266
object-oriented
programming, 14, 100, 108, 211, 238
object-relational mapper, 78, 79, 243, 320, 321
reinvention, 6
versus SQL, 238
obligation, 87, 108
Obsolete attribute, see attribute
Occurrence class, 215–218
office, 279
home, 284
open, 284, 285
own, 284
one-time code, 112
open-source software, 278, 285
OpenAPI, 205
opening hours, 168, 169
operations specialist, 177
operations team, 78
operator
greater-than, 123
greater-than-or-equal, 123
less-than, 123
less-than-or-equal, 123
minus, 302
null-coalescing, 104, 133
null-forgiving, 92, 144
ternary, 104
unary, 302
Option, 146
order
ascending, 122
ordering, 227
organisation
healthy, 32
rhythm, 193
unhealthy, 32
ORM, see object-relational mapper
outcome
actual, 56, 97
adverse, 127
direct, 178
expected, 56, 72, 97, 116
falsifiable, 97
improvement, 17, 29, 32
negative, 178
positive, 178
predicted, 97
quantitative, 97
successful, 13, 124
versus process, 178
output, 70, 100, 103
indirect, 229
parsed, 148
terminal, 252, 253
to input, 262, 264
type, 163
over-engineering, 215
overbooking, 246, 247, 268
test, 116, 117, 226
overload, 213
add, 212, 213
return-type, 217
overlogging, 272
overtime, 192
P
package, 30, 283, 318, 319, 321–323
author, 282
data access, 321, 322
distribution, 282
encapsulation, 156
reusable, 44, 45, 301
test, 322
update, 282, 283
version, 282
package manager, 282
package restore, 237
pair programming, 189–192, 199, 295
rotation, 190
parameter, see also argument, 152
how many, 153
query, 50
swap, 242, 243, 268
Parameter Object, 153
parameterless constructor, see constructor
Parametrised Test, see test
params keyword, 170
parsing, 144, 145, 147, 148, 173
partial function, 148
password, 296
pattern language, xxiii
pause point, 16
peasant, 290
performance, 201, 287–290, 292
fixation, 292
issue, 58
performance monitoring, 267
permission, 198, 293, 299
persistent storage, 77
personal computer, 11
personally identifiable information, 296
perverse incentive, 132, 292
petri dish, 307
phase, 5
act, 56, 57, 69, 97, 115, 124
arrange, 56, 69, 115, 124
assert, 56, 73, 91, 97
construction, 5, 6
design, 6
green, 97, 98, 104, 107, 125
programming, 5
red, 97, 107, 125
refactor, 96, 98, 104
phone number, 112, 113
physical activity, 279
physical design, 4
physical object, 12, 13, 156
physical work, 279
physics, 44
PII, see personally identifiable information
pilot, 16–18
test, 16
pipeline, see deployment pipeline
pixel, 258
plain text, 54
document, 61
planning, 5, 6, 13, 49
platform, 258, 309
defect, 298
plot of land, 87, 290
poka-yoke, 159, 321
active, 159
passive, 159
policy, 21, 198
politeness, 198
polymorphism, 146, 172, 229, 268
Pomodoro technique, 276, 277
pop culture, 12
ports and adapters, 318, 319, 323
positive number, see number
POST, see HTTP
PostAsync method, 66
postcondition, 226–228
contract, 108
guarantee, 108
invariant, 109, 144
Postel’s law, 103
weaken, 228
Postel’s law, 103, 106, 109
Postel, Jon, 103
Postman, 82
PowerShell, 22
precondition, 143, 145, 156
check, 105, 144, 145
contract, 108
invariant, 109, 144
Postel’s law, 103
responsibility, 108
strengthen, 232
weaken, 212, 227
predicate, 260, 262, 263
predictability, 264
prediction, 37, 97, 236, 237
PRINCE2, 276
private, see access modifier
probability, 127
problem
address, 236, 237
alternative solution, 9
dealing with, 236
detect, 252
disappear, 236
explaining, 239
manifestation, 236, 272, 278
reaction, 236
reproduction, 246, 251
solving, 235, 238
stuck, 238
unanticipated, 193
process, 275, 291
agile, 284
approval, 190
compilation, 167
external, 299
formal, 308
iterative, 197
long-running, 102
mistake-proof, 159
subconscious, 279
versus outcome, 178
procrastination, 276
product owner, 177
production code
as answer to driver, 88
bug, 228
change, 224, 228
confidence, 224
coupled to test code, 228
edit, 203
refactoring, 227, 229
rule, 58
productivity, 191, 235, 278, 281, 285
deleting code, 132
long hours, 279
measure, 279
metric, 132
negative, 279
personal, 279, 285
tip, 280
profit, 35
Program class, 22, 26, 27, 150
programmer, see also developer
good, 45, 176
irreplacable, 113
legacy code, 113
maintenance, 105, 309
other, 177, 310
responsibility, 295
single, 192
suffering of, 129
third-party, 326
user-interface, 188
programming by coincidence, 236, 237
programming language
advanced, 14
C-based, 135
components, 258
cross-platform, 36
density, 134
emulator, 39
functional, 266
high-level, 298
keyword, 133
layout, 135
learning, 18, 279, 280
mainstream, 320, 321
new, 40
statically typed, 157, 161
tools, 24, 25
verbosity, 21, 134
progress, 12, 14, 35, 45
project, 4–6
project management, 6, 37
proper noun, 256
property, 301
C#, 301, 302
declaration, 72
getter, 143
read-only, 72, 75, 172, 176
Visual Basic, 301
Property attribute, see attribute
property-based testing, see test
prophylaxis, 134
prose, 180, 181
Pryce, Nat, 7
psychology, 42
pull request, 197, 198, 285
big, 134, 194, 198
punch card, 289
punctuation, 180
pure function, see also referential transparency, 237, 264–
266, 273, 274
PureScript, 166
purpose, 36, 37, 44, 258, 296
PUT, see HTTP
puzzle, 33, 43
Q
quality, 129, 301
build in, 159, 240
essential, 100
internal, 31, 35, 37, 98, 154
better, 131
low, 40
quality gate, 31, 32
quantifiable result, 36, 97
Query, see also Command Query Separation, 166
composition, 262
constructor, 262
deterministic, 264, 265
example, 176, 262, 263
favour, 166
non-deterministic, 264, 266
parameter, 50
side effect, 261, 262
type, 163, 171
queue, 50, 249, 299
QuickCheck, 301
quicksort, 45
R
race condition, 246–248
Rainsberger, J.B., 47
RAM, 39, 45, 112
random number, 273
random number generator, 264
random value, 301, 302
range, 148, 212, 213
readability, 41, 281
code review criterion, 196
nudge, 135
optimise for, 40, 79
reader
future, 59, 160, 163
readme, 168
real world, 29, 100, 258
reality, 6, 10, 52, 192, 290
physical, 6
reboot, 236, 237
receiver, 160
recursion, 89
Red Green Refactor, 96, 97, 125, 128, 224
execution time, 244
red phase, 103, 107
Reeves, Jack, 5, 13
refactoring, 98, 203
Add Parameter, 228
backbone of, 224
big, 220
candidate, 139
code ownership, 187
commit, 229
database, 245
Extract Method, 187, 227, 228
IDE, 234
Inline Method, 187
legacy code, 114
Move Method, 143, 227
opportunity, 125
prophylactical, 134
Rename Method, 218, 227
Rename Variable, 227
safe, 227, 228
test, 231
test code, 224, 232, 234
apart, 229
to property-based test, 301–303
upon rot, 325
toward deeper insight, 209
Refactoring (book), 143, 223, 224, 227
reference type, see also null, 28
referential transparency, 264, 265, 273
regression, 227, 228
likelihood, 126
prevention, 55, 61, 127, 243
relationship type, 206
release, 219–221
canary, 300
Release configuration, 22, 25
release cycle, 5
repeatability, 272
repetition, 251
Repository, see design pattern
repudiation, 293, 296
research, 5, 38
resiliency, 298–300
REST, 66
restart, 236
restaurant owner, 294, 296
RESTful, 205
RESTful Web Services Cookbook (book), 116
return on investment, 299
revelation, 278
review, 13, see code review
reviewer, 195–198
rework, 220
Richardson Maturity Model, 66
risk, 299
risk assessment, 127
robot, 156
industrial, 258, 327
role
claim, 297, 298
object, 70
rollback, 246
roof, 6
roofer, 9
room
dark, 7
root cause, 255
Roslyn, 26, 29
rotation, 56, 57
routine, 194, 279
routing, 151, 311
rubber duck, 239, 240, 251
rubber stamp, 194, 198
Ruby, 89, 282
RubyGems, 282
rule
against decay, 131
analyser, 26, 27, 30, 31, 57, 139
breaking, 131
business, 118, 124, 138, 290
encapsulation, 70, 72
Command Query Separation, 166
disable, 60
documentation, 58
extra, 264
formatting, 256
hard, 132
line height, 135
machine-enforced, 31
motivation, 28
redundant, 132
threshold, 131, 132
versus food for though, 89
rule of thumb, 10, 182, 204, 210, 242
running, 278
Russian dolls, 268, 269
S
sabotage, 119, 233
safety net, 224, 228, 234, 244
salary, 21
scaffold, 20
scalar, 88, 89, 119
schedule
certificate update, 284
package update, 283
synchronisation, 190
team, 282
school, 160, 177, 280, 281
science, 44, 97, 98
scientific evidence, 13
scientific method, 97, 236, 237, 256
scientist, 3, 44
screen, 42, 239, 258, 281
Scrum, 276, 283
sprint, 283
retrospective, 282
SDK, 282, 326
sealed keyword, 27
seating
bar-style, 118, 168
counter, 117
overlap detection, 174
second, 138, 168, 169
single, 117, 138, 168
security, 271, 287, 288, 290, 292, 300
balance, 296
mitigation, 292
security by obscurity, 294
Seeing Like a State (book), 290
self-hosting, 55, 324
self-similarity, 154
Semantic Versioning, 218, 219
semicolon, 135
sender, 160
sensitivity, 231, 290
separation of concerns, 257, 268, 274, 314
serialisation, 64, 249, 250, 325
server, 21
setter, 108, 143
seven, 46, 136–138, 151–154
magical number, 39
proxy, 46
threshold, 130, 131, 133
token, 39, 133
shared code, see code ownership
shell script, 22
shifting sands of individual experience, 11, 13, 93, 99, 235,
256
shopping, 279
shower, 278
side effect, 162, 164–166, 171, 258, 259, 261–266
constructor, 262
Haskell, 323
hidden, 43
logging, 273
sign-off, 13, 23, 198, 199
signal, 29, 247
signature
digital, 296
method, 145, 146, 162–164, 166, 170, 171
identical, 213
Simple Made Easy (conference talk), 238
simplest thing that could possibly work, 75, 117, 215
simplicity, 46, 238
simulation, 13, 21
single point of failure, 187
SingleOrDefault method, 124–127
Singleton lifetime, see Dependency Injection
skill, 199
decomposition, 155
legacy, 113
literary composition, 160
situational, 8
specialised, 9
troubleshooting, 235
slice
vertical, 49–52, 54, 60, 61, 77
first, 64, 85
happy path, 64
purpose, 64
small step, 61, 88, 194, 220
SMTP, 102
snapshot, 183, 184, 187
social media, 258
software
reusable, 45
successful, 4
sustainable, 67
unsuccessful, 4
software craftsmanship, 8–10
software crisis, 11, 14
software developer, see also developer
collaboration, 189
professional, 31
skill, 8
software development
asynchronous, 285
highest-ranked problem, 182
history, 14, 15
industry, 9, 13, 14, 45
age, 3
improvement, 8
management, 292
process, 52, 276
latency, 192
regular, 35
professional, 29, 235
reality, 203
project
bad, xxiv
sustainable, 40
team, 177, 287
software engineering, 34, 35, 37, 41, 44–47
aspirational goal, 11
classic, 308
conference, 11
deterministic process, 125
pocket, 11
practice, 182
process, 177
science, 97
traditional, 300, 308
SOLID principles, 300
sort order, 256 Danish, 256
sorting algorithm, see algorithm
source control system, see version control system
spaghetti code, 261, 285, 319
special case, 212, 237
specialisation, 188
Speculative Generality, 52
spelling error, 25
split screen configuration, 135
spoofing, 293, 294
SQL, 78, 212, 238, 245, 299
named parameter, 295
script, 315
SELECT, 123
SQL injection, 293, 295, 296, 299
SQL Server, 78, 299
SSTable, 45
Stack Overflow, 14, 240, 251, 280
stack trace, 309
stakeholder
Continuous Delivery, 276
disregard for engineering, 31
feedback from, 49, 85
involvment, 308
meeting, 280
prioritisation, 290
security, 292, 293
stand-in, 73
standard
de-facto, 18, 179
standard output, 50
Startup class, 23, 27, 55, 63, 76, 81, 83, 150, 173, 310, 311,
313, 315
constructor, 82
Stash, 178
state
application, 78, 121, 164
change, 164, 165
local, 165
consistent, 218
illegal
unrepresentable, 159
inspection, 230
invalid, 106–108, 159
mutation, 106
object, 106, 164
system, 249
transformation, 88
valid, 106, 144, 156
stateless class, 76, 80, 173
statement
formal, 41
statement completion, 281
static code analysis, see code analysis
static flow analysis, 144, 147
static keyword, 27, 67, 139, 142, 147
statistics, 178
steering wheel, 41, 42
stored procedure, 299
Strangler, 210, 220
class-level, 215–217
method-level, 214
strangler fig, 210, 211
STRIDE, 292–294, 300
string comparison, 255
stringly typed code, 58, 164, 242
stroll, 239
struct keyword, 207
structural equality, 72
stub, 73
subdirectory, see also directory, 314, 315
subroutine, 39
subterfuge, 32
subtype, 227
Subversion, 18, 178
suffering, xxiv, 129
sum type, 160
sunk cost fallacy, see fallacy
SuperFreakonomics (book), 132
supertype, 227
support agreement, 78
surgeon, 17
surgery, 17
survey
geographical, 127, 128
sustainability, 34–37, 40, 44, 45, 47, 114
versus speed, 67
SUT, see System Under Test
SUT Encapsulation Method, 66
Swagger, 205
Swiss Army knife, 158, 164
switch keyword, 133
syntactic sugar, 143
system
edge, 265, 266
restore, 284
running, 268
System 1, 42, 43, 279
System 2, 42, 43
system tray, 277
System Under Test, 66, 69, 301, 316, 325
coupling to test, 107
description, 128, 304
sabotage, 233
state, 230
triangulation, 127
T
tab, 315, 316
tagged union, see sum type
take-off, 16
tampering, 293–295
task
big, 276
complex, 16
getting started, 276
tautology
assertion, 97, 233
TCP, 103
TDD, see test-driven development
team
change, 187
high-performing, 5
low-performing, 5
team coupling, 308
team member
new, 111, 150
TeamCity, 24
technical debt, 7, 8, 178
technical expertise, 31
temperature, 326
terminal, 135
terrain, 291
test
acceptance, 61
add to existing code base, 24
as measurment, 127
automated
as driver, 53
as guidance, 167
database, 83
ease, 19
favour, 82
system, 82
boundary, 69, 76, 83
coverage, 118, 223, 250
deterministic, 246, 250
developer, 21
example, 300
exploratory, 208, 241
failing, 63, 96, 241
high-level, 65
in-process, 243
integration, 54, 208, 242–246, 297, 318
iteration, 96
manual, 82, 85
non-deterministic, 246, 248, 250
parametrised, 89, 90, 107, 300, 301
append test case, 119, 225
compared to property, 301
passing, 96, 97
property-based, 53, 279, 301–305
refactoring, 301
regression, 241
revisit, 116
slow, 243, 246, 249
smoke, 82, 85
state-based, 73
test case, 90, 91
append, 225
before and after, 245
comprehensive, 304
exercise, 250
good, 119
redundant, 128
single, 88
test code, 224
change, 234
coupled to production
code, 228
duplication, 61
edit, 224, 225, 228, 232, 234
maintenance, 234, 325
problem, 91
refactoring, 227, 229, 231, 232, 234, 326
rotate, 56, 57
test data, 303
Test Double, 73
Fake Object, 73, 84, 122
Test Spy, 229, 231
test framework, 90, 282
test library, 58
test method, 89, 119
add, 225
orchestration, 248, 249
test pilot, see pilot
test runner, 58
Test Spy, see Test Double
test suite, 53
build script, 55
execution time, 244
failing, 248
noise, 247, 248
safety net, 224
trust, 223, 248
Test Utility Method, 65, 324–326
test-driven development, 53, 63
acceptance, 61
beginner, 119
coaching, 191
enabling, 54
execution time, 244
mob programming, 316
one among alternative
drivers, 76
outside-in, 53, 61, 64, 68, 78
poka-yoke, 159
scientific method, 97, 98
security feature, 251
success story, 240
teaching, 119
technology choice, 78
triangulation, 127
Test-Driven Development By Example (book), 116
text file, 21
textbook, 280
The Leprechauns of Software Engineering (book), 13
The Pragmatic Programmer (book), 9, 279
Theory attribute, see attribute
thinking
deliberate, 42
effortful, 43
thread, 57, 58, 76
multi, 250
race, 246, 247
single, 249
thread safety, 80, 173
threat, 292, 299
identification, 299
mitigation, 295, 296, 298
threat modelling, 292, 293, 299, 300
threshold, 130–133, 135, 306, 307
aggressive, 154
throughput, 326
tick, 122
time, 227, 264, 266, 273, 306
management, 238
of day, 264
personal, 279
wasting, 276, 279
time-boxing, 238, 276, 277, 280
timeout, 248, 250
TODO comment, 67
tool, 24, 72
analyser, 24–30, 53, 76, 88, 302
warning, 28, 29
GUI, 82
linter, 24
topology, 21
Tornhill, Adam, 305
touch type, 280, 281
tradition, 9, 10, 268
traffic, 293, 298, 326
transaction, 87, 183, 246, 249, 250
roll back, 246
TransactionScope class, 250
transformation
atomic, 88
code, 88, 89, 91, 115, 119
Data Transfer Object, 70
input, 50
Transformation Priority Premise, 75, 89, 115, 119, 128
tree, 151, 211, 314
B, 45
dead, 211
fractal, 151, 152
hollow, 211
host, 210
leaf node, 151
LSM, 45
Trelford, Phil, 158
triangulation, 119, 123, 127
geometry, 127
troubleshooting, 235, 236, 272
debugging, 256
experience, 255
ordeal, 8
superpower, 256
support future, 272
understanding, 268
trunk, 151, 184
trust, 87, 224, 248
try/catch, 92
TryParse method, 98, 99
Twitter, 244, 277
two-factor authentication, see authentication
type
anonymous, 64
custom, 170
generic, 216
polymorphic, 229
static, 164
wrapper, 302
type declaration, 28
type hierarchy, 227
type inference, xxvi
type information, 157
static, 170
type signature, 162
type system, 106, 157
static, 28, 100
type-driven development, 53
TypeScript, xxiv
typist, 5, 281
typo, 187, 196, 281
U
ubiquitous language, 169
unauthorised access, 293
understanding, 235–238, 241, 251, 252
bug, 40
computer, 45, 176
difficult, 35, 40, 46
easier, 216
human, 45, 176
struggling, 182
undo, 18, 19, 186
unintended consequence, 132
unit, 68, 69, 107
Unit of Work, see design pattern
unit test, see test
definition, 68
universal conjecture, 98
urgency, 283
Uri class, 58, 59, 66
URL, 66, 83, 205, 294, 296
documented, 205
opaque, 206
template, 66
UrtCop, 26
USB, 159
Usenet, 280
user, 4, 52, 296
regular, 293, 299
user code, 151, 298
user group, 31
user interface
before database, 6
feature flag, 209
slice, 50
using directive, 21
V
vacation, 41, 187, 191, 205
validation, 77, 92, 147, 154, 261
email address, 102
input, 115
object-oriented, 144
validation link, 102
validity, 99, 102, 106, 108, 147
value, 36, 37, 192
hard-coded, 303
run-time, 273
Value Object, see design pattern, 72
value type, 207
var keyword, xxvi, xxvii
variable, 75, 88, 89, 119
count, 153, 154
global, xxv, 43
local, 153
name, 196
VBScript, 44
vendor, 78
version
language, 282
major, 219, 221
new, 282
old, 282
platform, 282
skip, 282
version control data, 305
version control system, 18, 19, 178, 305
centralised, 18, 19, 182
CVS, 18
distributed, 18, 186
secrets, 82
Subversion, 18
tactical advantage, 186
vertex, 314
vicious circle, 193
Vietnam, 6
view
high-level, 151
materialised, 299
vigilance, 320
vine, 210, 211
violence, 210
virtual machine, 21
Visitor, see design pattern
Visual Basic, 44
property, 301
Visual SourceSafe, 183
Visual Studio
add null check, 76, 81
auto-generated code, 21–23, 25, 72
build configuration, 25
code metrics, 105, 133
developer, 30
generate constructor, 72
generate Equals and GetHashCode, 72
Go To Definition, 315
IntelliSense, 157
project, 30, 54, 318
solution, 30, 54, 245
test runner, 58
void keyword, 162, 165
VT100, 135
vulnerability, 293, 294, 296, 299
W
wait time, 193
maximum, 194
walking, 239, 277, 279
Walking Skeleton, 20, 54, 60
warnings as errors, 25, 26, 29–32
as driver, 53, 57
cost, 67
weak code ownership, see code ownership
web site, 51
Weinberg, Gerald M., 289
what you see is all there is, 43, 45, 152, 175
while keyword, 133
Windows, 19, 22, 268, 277, 315
wizard, 20, 54
work
design, 6, 278
detective, 106
human, 13
intellectual, 5, 42, 279
physical, 279
project, 4
skilled, 8
uninterrupted, 276
unplanned, 192, 193
work from home, 284
work item, 275, 276, 283
work item management, 178
workaround, 207, 255
worker, 5
Working Effectively with Legacy Code (book), 113, 223
workshop, 292
worse is better, 36, 37
wrapper, 207, 242, 269, 302, 304
writer
single-thread, 249
WYSIATI, see what you see is all there is
X
X out names, 162–164, 260
x-ray, 307
X.509 certificate, see certificate
XML, 250, 326
XP, 276, 284
xp_cmdshell, 299
xUnit Test Patterns (book), 224
xUnit.net, 55, 90, 245, 301
Y
Yoder, Joseph, 153
Z
zero, 102, 106, 107
zero bugs, 240
zero tolerance, 25
zone, see also flow, 42, 277, 278
zoom, 148, 149, 151, 152, 154
context, 314, 317, 324, 325
example, 175, 265
navigation, 314, 317, 325
out, 265
Code Snippets
Many titles include programming code or configuration
examples. To optimize the presentation of these elements,
view the eBook in single-column, landscape mode and
adjust the font size to the smallest setting. In addition to
presenting code and configurations in the reflowable text
format, we have included images of the code that mimic the
presentation found in the print book; therefore, where the
reflowable format may compromise the presentation of the
code listing, you will see a “Click here to view code image”
link. Click the link to view the print-fidelity code image. To
return to the previous page viewed, click the Back button on
your device or app.