I celebrated my FullStoryVersary recently, marking the first year of my career as a software engineer after graduating from the Georgia Institute of Technology in 2019. I work on the FullStory app client code and the goal of my team is to help scale frontend engineering at FullStory. Our responsibilities include converting our app to React from a legacy UI framework, mapping out the high-level architecture of the client code, and strategically providing space for our engineers to implement client-side features effectively. The most important attribute we cultivate as a team is agility. Agility at FullStory isn't just about moving quickly; it's about making the right trade-offs, with clarity of purpose and the confidence to make big changes.
Thanks to working closely with a (super) senior engineer, who is also my team’s tech lead, I learned the principles of software engineering at FullStory, and how they were different from my way of working and thinking, whether it be code reviewing, communicating, writing design docs, or writing code. We pair-programmed a lot, which is when I gained insight into our team’s thinking flow and how we break down extremely complex problems.
Reflecting back on my first year of my career, I have observed a few valuable principles that I use as a foundation for my growth. I’m still not an expert on any of these but I hope to cultivate them as much as I can. I'm writing these out with the hope that others might find them useful.
Take Initiative, Selflessly
Power comes not from knowledge kept but from knowledge shared. - Bill Gates
Whether it be leading a project, onboarding a new engineer, or cleaning up technical debt, volunteer to do it and do it with an open mind and open heart. These initiatives helped me stretch out of my comfort zone in order to grow.
When faced with some obsolete code, be confident about changing or removing it. When I first joined FullStory, I was hesitant to question things. I would try to work around the code by writing a custom solution on top of the existing core code to solve my problem. This helped get my work done quickly while avoiding conversations with other people. The downside of this is I added another layer of complexity to the codebase. What I noticed from the senior engineers is that they try to fix the existing core code instead, rather than implement work-arounds. If needed, they will ask for others’ opinion before proceeding. Working with this mindset, I have reduced tech debt while shipping my features at the same time.
Once I saw how effectively my peers shared feedback with each other, I began volunteering feedback of my own. Volunteering to help others opened my mind and made me become a valuable contributor across teams. What I gained from countless hours of pair-programming is the insights of how people think when they build software and the difficulties they face when using the component library my team builds and supports.
Define a stopping point
We move fast, prioritize goals, and set out a roadmap to deliver the best and biggest impact for our customers. It can feel never-ending and overwhelming. Designers draw fancy design mocks. Product managers sketch out roadmaps and deadlines. As a junior engineer, I easily found myself operating with the assumption that perfection was the primary goal. After going through a few project iterations, I realized we can't build software with that mindset. It is equally unprofessional to promise impossible deadlines and trade off the quality of our software.
We, as a company, have the responsibility to serve our customers what they need when they need it in a timely manner. It's important to stop when it's good enough and get it into customers’ hands to gain valuable product feedback as soon as possible. The feature might not be perfect. Who knows when it will be perfect? Knowing software is never perfect, it’s important to prioritize requirements so that the most important features get out of the door first. The earlier we ship a feature, the quicker we iterate to bring it to completion.
Communicate Generously and with Empathy
As engineers, a large part of our day is spent communicating. We communicate in many ways: Slack, design docs, pull requests, or meetings. Communication is as important here as writing code, and I had to learn how to communicate empathically. Here at FullStory, one of our Watchwords is empathy and it is rooted in our culture. Empathy means making an effort to understand how others feel. When interpreting someone’s statement, I started to assume that the best possible interpretation of their statement was the one that they meant to convey. In other words, when someone reaches out for help, I assume they
- Have exhaustively used all of their knowledge
- Have done all research
- Are just human beings asking for help because they are unable to answer their questions using all the knowledge available to them.
Here is an example of what it means to lack of empathy in communication
X: Why did we decide to go back to using StoryBook for our UI library?
Me: My last company used StoryBook and it was pretty cool. So we decided to try it out.
X: Ok, but why did we replace MDX with StoryBook? What problems does StoryBook solve that MDX doesn't?
Me: Hmm. You should have asked that...
And here is an example of what it means to have empathic communication.
X: Why did we decide to go back to using StoryBook for our UI library?
Me: I'm guessing you're asking for context why we decided to replace MDX with StoryBook. Stop me if I misunderstood your question.
X: You're correct. I'm just curious.
Me: So, after a few weeks of using MDX, we constantly ran into issues where it doesn’t detect type errors when there’s a change in the components’ API. The reason is that MDX does not support type checking. As a result, breaking changes in our UI library were shipped without us being aware of it. Moving to StoryBook ensures that all code is strictly type checked before being shipped to production.
In generous communication, we check our assumption about the question and the asker’s context and intention. Notice the difference in how fewer words are exchanged before we dive into the answer. Communication with empathy can make us better at understanding others, and improves our ability to construct our own arguments.
When communicating, be aware of the curse of your own knowledge. Once you know something, it can be easy to falsely think others are on the same page. So it's better to err on the side of over-communicating (when possible) to keep aligned, especially now that everyone has transitioned to working from home these past few months.
Empathic Pull Requests
As mentioned previously, pull requests are one of the ways we use to communicate daily. From my experience, we can tell a lot about the quality of an engineering team based on how their pull requests are constructed and reviewed. Code reviews are a critical part of the software development process at FullStory, and getting the communication right between PR authors and approvers is extremely important.
Empathy reminds us that winning an argument is not the same as getting durable buy-in from others. In order to show empathy when reviewing code, here are a few things I remind myself to do.
There's a distinction between suggestion and order. Instead of telling the author how they did something incorrectly, we might offer suggestions and ask what they think of it. For example, instead of saying
Don't hard-code these values, move them to variables
We would say
Should we declare variables for these values so that the code has more context?
Starting with a question reminds me that there might be a good reason behind something we disagree with. As a reviewer, we might lack context. The pull request might include changes that are totally unrelated. If we find something hard to grasp, we should suggest the author provide more context.
Consistency makes code easier to read and understand and encourages collaboration across teams. For example, at FullStory, we have opted to use functional components over class components for their simplicity. Functional components are much easier to read and we end up with less code. Future versions of React will have performance enhancements that will only be supported in functional components.
Code reviews are an opportunity to promote consistency. They provide a way to identify and establish patterns that our engineers are using. This allows any engineer to transition to different pieces of the codebase and start contributing right away without too much time spent ramping up. With consistent patterns and practices, it’s easier to dive into someone else’s code.
Make Small Pull Requests
All of our code is hosted in a monorepo. When you’re an author, making a pull request small is the best way to speed up the reviewing cycle. It is ok that a pull request changes multiple files and fixes multiple bugs as long as they all represent a single context. When a pull request represents a single context, it’s easy for the reviewer to spot any potential bug, which leads to better quality code. Code reviewing is only half the reason why we construct smaller pull requests. Once merged, deploying small changes provides better stability. There are fewer interactions you need to go through to validate your changes in staging and production and fewer execution paths you need to test. When things go wrong or your code breaks the system, it’s straightforward to roll back the small surface area without breaking or sunsetting other things.
Give Your PR a Good Title and Description
The reviewer isn’t involved in authoring the PR. They do not know what issues you ran into while working on it. It is important to give a good title and description. Good titles and descriptions provide context. It helps reviewers know what they are getting into before they start. A good description includes:
- A link to any related resources such as user stories, other pull requests, or slack threads.
- A request for specific feedback such as questions about performance or maintainability, or whether the components are broken down in an understandable way.
- An explanation of what is going on in the pull request. What did we fix or what feature is being enhanced? What arc of work does the pull request belong to?
We don’t always need all of these but we aim to provide as much context as possible. By providing more context, the reviewer won’t need to spend time gathering the context themselves. Instead, they can focus their energy on reviewing the code itself.
More to come...
The past year at FullStory has had a great impact on my career. This article is just a small fraction of what I have actually learned. I can’t wait to see what the upcoming years unfold and where we go next.