Pay It Forward

What to test in unit testing

Unit testing became popular with Kent Beck’s introduction of the JUnit testing framework for Java. But because the notion of “unit” isn’t precisely defined, it’s easy to test every blessed thing in one’s code, especially when engineering managers start to see bugs in code and start demanding that tests offer “complete code coverage”.

There are two big problems with writing tests for code coverage. First, there isn’t a useful definition of “code coverage”. One popular definition–touching every line of code in a file–is next to useless; the most rigorous definition–following every possible code path–is patently infeasible. Second, it provides no sense of proportion, no sense of what is and isn’t important in an application. So it’s not surprising that engineers have largely produced massive amounts of practically worthless lines of code in the service of code coverage.

This was the background against which James Coplien wrote his justly famous notorious essay, “Why Most Unit Testing Is Waste”. It’s worth a close read, but the main thing worth taking away is that unit tests are useful mainly for testing algorithmic logic, that is, for testing stateless functions that determine specific outputs from specific inputs. Everything else worth testing should probably go into an integration test.

A summary of Edsger Dijkstra's "Why numbering should start at zero"

This is cross-posted from my reading-notes blog, because I thought it would be nice to see one of the giants of computer science discussing clearly and simply a topic so fundamental to how we work with arrays.

Addendum: I don’t note in the post Dijkstra’s observation that some prominent (old) programming languages, such as FORTRAN, ALGOL-60, and Pascal, do not follow the conventions he recommends!

How should we denote an arbitrary sequence of consecutive natural numbers, i.e., the set of non-negative integers? Edsger Dijkstra considers the four exhaustive possibilities, using the example 2, 3, …, 12:

a) 2 <= i < 13
b) 1 < i <= 12
c) 2 <= i <= 12
d) 1 < i < 13

He notes that only in a) and b) is the difference between the bounds equal to the length of the subsequence. This is important for being able to calculate (easily, or intuitively) the length of the sequence from the notation, so is a reason to exclude c) and d).

Then he notes that because there is a smallest natural number (0), b) cannot denote the empty sequence (a sequence with 0 elements) without using non-natural numbers, so is a reason for preferring a) to b):

a) 0 <= i < 0
b) -1 < i <= -1

(We could try something like 1 < 0 <= 0 to denote an empty sequence using notation b), but that would violate the requirement that nondecreasing sequences be denoted using left-to-right nondecreasing numbers.)

This brings us to the question of how to denote the index of the starting element of a sequence of length N: 0 or 1? If we use the convention we’ve already decided upon, we get the following when we start with 0 and 1 respectively:

0 <= i < N
1 <= i < N + 1

Echoing the prior discussion of denoting sequences, choosing 1 requires using a number larger than the number of elements in the sequence; choosing 0 does not. Hence, as Dijkstra observes, we can define an element’s index to denote the number of elements preceding it.

Leading is doing

I’m inspired here by Camilla Montonen’s skeet:

The longer I am in this industry, the more I realise that there is leadership in title or name and then actual get shit done leadership. If you see a failing test or a codebase that needs linting and you fix it, that’s leadership.

When I started working as an engineer, a lot of my work came from managers: build this feature, fix that bug. You might think it was because, as a new engineer, I needed lots of help, and that was true. But it was also because I didn’t know anything about the company I was a part of–I didn’t know what we were building, or why it was built that way.

As I grew more capable and experienced, I also started to understand what we were building and why were building it. That is: I started to understand how our work furthered our company’s goals. And that is when I began to see gaps in what we needed, problems with our existing products, and bugs and annoyances that were impeding our progress.

And that was when I realized I had a valuable perspective on what we were doing as an engineering team. I started to see how I would solve those problems, and as I discussed them with my teammates, I learned that many of them didn’t know the problems existed, or didn’t realize that solving them was a thing we could do.

So if you want to know why good engineers get paid lots of money, at bottom it’s not because they have strong engineering skills. It’s because they know how to use their skills to effectively further the goals of the company.

OK, but what does any of this have to do with leadership?

Effective leadership is mostly just seeing what needs doing and ensuring it gets done. If you’re in a management role, that involves coordination and managing people; but it’s no less leadership if it’s fixing a test, linting a codebase, or making a painful manual task automated.

See a thing that needs doing, then within the scope of your power and authority, get it done. That’s leadership.

Readings: becoming a senior engineer

For folks wanting to understand what it means to be a “senior” engineer, here are a few useful articles you can study.

The canonical statement is by John Allspaw: On Being a Senior Engineer. It is worth reading carefully, more than once.

Camille Fournier has a great piece on becoming senior: The Senior Shift.

Cindy Sridharan’s piece on understanding your org is also critical, for understanding how to define your role: Know How Your Org Works.

Finally, Will Larson’s online resource staffeng.com further clarifies the senior role by delving deeply into the senior+ roles of staff and principal.

Happy reading!

I posted a quick note on BlueSky about what leadership in engineering looks like here.

On "networking"

It’s lore in the working world that we all must “build networks”. This evokes images of dreary business lunches and awkward LinkedIn DMs. But strip it of business trappings, and networking reduces to people getting to know one another.

As a budding software engineer, this can be terrifying. Maybe tech is like one of those fancy clubs with a bouncer who looks you over and refuses to let you in? That’s imposter syndrome in a sketch: ever being the wallflower at the party. But it’s also a natural anxiety about making yourself vulnerable, and making yourself vulnerable is fundamental to all relationships that matter.

I’m not saying you should get over yourself. But, you know, when you have a job, your coworkers will be working with you, the real you. If that’s to happen, you’ll need to peel yourself off the wall and mix.

Hitting the ground running

Don’t worry about “acing the coding interview”, focus on producing quality code instead

So: you’re a CS program or bootcamp graduate, and you’re wondering what you should be focusing on to get interviewed. A lot of people recommend that you focus on coding skills: data structures, algorithms, LeetCode problems, etc. The usual reason given for this is that programming interviews tend to focus on these sorts of skills.

Personally, I think coding interviews are as a rule conducted so poorly as to have practically negative value, but that’s a post for another time. That said, the coding interview happens only after you’ve gotten an interview. How can you show yourself to be someone that should be interviewed?

Given the outsized importance many budding engineers assign to coding interviews, showing off coding skills in your resume or your GitHub repositories would seem to be the natural thing to do. Sadly (or maybe not so sadly!), precious little of your data structures knowledge or LeetCode mojo will be called upon in any software engineering job you get. Most of the time you will be working with other engineers on not-especially-difficult coding tasks using the tools of the trade. That is to say, learning to code is only the beginning of what you need to learn to be an effective software engineer.

My own humble opinion is that, from the perspective of a prospective interviewer, demonstrating some facility with how working engineers get their work done will make a more significant impression than your awesome hack3r skillz. That is, you’re much better off focusing on how to create production-quality code in a collaborative environment.

What does this mean? Well, production-quality code is code that (1) fully meets the requirements for the task, (2) has been sufficiently well tested via accompanying automated tests, and (3) meets the team’s coding and formatting standards. And a collaborative environment is a team of engineers and relevant engineering stakeholders, each of whom you’ll need to be work with.

Learn to use the tools of the trade

How do you demonstrate this sort of facility, without already having had a software engineering job? The closest thing you can get to this is to demonstrate your facility with the tools engineers use to collaborate to produce production-quality code. These tools include, but are not limited to:

  • source control management (git, almost always)
  • code formatters and linters (Go has gofmt, Python has pylint, flake and ruff)
  • containerization software (Docker)
  • IDEs (e.g., Visual Studio Code)
  • terminal shell languages (e.g., bash)
  • unit testing frameworks (virtually every language has at least one)
  • deployment frameworks (e.g., GitHub Actions, GitLab CICD)

There are many different ways of combining these tools to get code into a production environment, but the following pattern is common:

  1. A shell script runs formatting and linting tools against code that’s been checked into a source code repository.
  2. Another script runs unit tests against that code.
  3. Finally, some automated framework is invoked to push the code into an application environment, often using Docker images.

To demonstrate this facitlity, then, you can create repositories that make use of these tools. For example, you can create a Python or Ruby web service, complete with automated unit tests, that runs in a Docker container and is deployed via GitHub Action to AWS or Google Cloud. The repository uses a simple branching strategy (I’ll cover git branching strategies in another post), and there are straightforward conditions preventing deployment when the code isn’t properly formatted, or has linting errors, or unit test failures. All of this is fully documented in the repository’s README.

Furthermore, anyone checking out the repository should be able to run all the (passing) tests and get a local version of the service running on their local machine within 15 minutes; 5 minutes would be ideal.

Speaking for myself, if someone wanting to work on my team had repositories like that, I would have no worries about their ability to contribute on my team.

A simple homework assignment

Request to shadow two or three people you know who are working at places you might want to work. When shadowing, pay attention to:

  • what IDE they use
  • what tests they write, and how they write them
  • how their code get deployed
  • what a typical code-review session is like
  • what kinds of developer tooling do they use, e.g., Docker, Github Actions, Terraform

Basically, you want to see how they get their work done. After shadowing several people at different shops, look for commonalities. These commonalities will give you starting points for what to focus on in your studies.

Welcome!

If you’re new here, maybe one of the people I’ve been working with as a mentor have sent you. (Thanks, person who sent you here!)

My name is Adam Vinueza. Currently (and for the foreseeable future, i.e., until things change), I work as an engineering manager at Strive Health. I’ve been working in the tech industry for about 20 years, and have worked in codebases large and small, excellent and craptastic, front-end and back-end, and written in multiple languages. (Python, Go, JavaScript, Ruby, C#, C/C++, Java, probably a few others I can’t remember.)

For the past several years (four?) I’ve been mentoring students at Turing School of Software and Design. I also mentor and lead engineers as part of my job.

This blog is intended as a resource for folks wanting some guidance about all things tech. I can’t promise to be right about my advice all the time, but I do promise to be honest and thorough and meticulous about what I say here.