Background
At my current job at Higharc, I have found myself in a position with a lot of influence, freedom to experiment, and purview to make fast and sweeping decisions. Some of this will surely change and diminish as we grow and bring in new folks with expertise in areas that may have formerly been in “my domain” when we were a smaller organization. For now, it still holds up.
I joined Higharc towards the end of 2020, as our 14th hire and 2nd full-stack web developer. Five years later, we are over ten times that size, and with the departure of our original CTO and a few others, I have found myself as our longest-tenured engineer. I’ve also seen the landscape of software development get turned upside down over the last few years due to the rise of LLMs. Few, if any at all, write software the same way they did even two or three years ago.
As an aside, I promise I will publish my ever-expanding draft post about my feelings on AI… eventually… perhaps?
My role has evolved over the years too. Starting as a full-stack web developer, moving into engineering management, out of management, back in again, and back out once more to land in my current position as a Staff engineer with a lot of purview over all-things-developer-experience, infrastructure, and tooling. I tend to enjoy working on tooling!
I share all this to say that, if nothing else, I’ve seen enough change in this one job to get a sense for what matters to me as a software engineer that genuinely enjoys writing code, tinkering, and making people happy.
My principles for building software
I have never been one to talk much about credos, values, and the like, often because those kinds of exercises can feel shallow or too subject to influence by the needs of the company. It should go without saying that companies have exactly one value: make money.
But I am no company; I am a person who genuinely enjoys the process of building enjoyable software that I can proudly put my name on and share with people. With all that out of the way, here are the things that matter most to me when it comes to this not-exactly-an-art-but-not-exactly-a-science craft I have devoted a lot of my life to.
1. Build on what you already have
This is the one I feel most passionately about, so it goes first. I’ve often been confronted with suggestions or pressure to replace something in its entirety when it isn’t working perfectly.
While there are moments where “blowing it up” makes sense, those moments should be infrequent. An obvious benefit of working incrementally is that you can make smaller and lower-risk improvements more quickly, then move onto other tasks. A less obvious benefit is that you give yourself the invaluable gift of time and space to learn more about what you want. Then, when you do hit a point where it’s time to start fresh, you can do so with a great deal of understanding of the problem space, what has been tried before, and what is or isn’t working. I feel steadfast in my sense that some of my most successful projects at Higharc, for example, were made better by my inclination to wait as long as possible to build that alluring “2.0” version. A recent example:
We started with a small set of “sandbox” environments for testing and demoing in-progress work, and that system was slowly expanded over years in the form of more servers, nicer tools for deploying them, and documentation before we decided to pursue the shinier alternative of “ephemeral” environments. Though having them from day one would have been great, I was able to understand exactly what developers on the team wanted out of these tools by the time I built out the new system.
2. Embrace stable libraries and open standards, avoid products at all costs
I’m a Linux nerd that lives in the command line for a reason. My development environment, desktop programs, and everything else that powers my day-to-day rarely change unless I want them to. As in, I might be able to go years without tweaking something. I try hard to avoid hype trains, keeping up with trends, or giving another $5 or $10 per month subscription service my credit card information.
This principle extends to how I write code and approach work. When you choose products over standards or open source alternatives, you sign up for the inevitable future moments where the company you’ve now embedded into your own software or workflow needs to raise cost, move things around, limit functionality, or otherwise change things in a way that is hostile to their users.
What this can look like in practice for people and organizations:
- Learn your cloud provider well, and build inside its ecosystem instead of leaving that to the third parties that provide slick (and expensive) tools on top of them. AWS, instead of AWS and Vercel, etc.
- Try out a modern Linux desktop environment like KDE instead of macOS or Windows and spare yourself having to re-learn things every update or search for “how to turn off Copilot” when you get bombarded with the latest and greatest annoyance Microsoft or Apple can shove in your face.
- Embrace the terminal! You might be able to write a function that wraps
ffmpeginstead of buying a $5 macOS app to compress a video.
3. Clear interfaces are everything
Perhaps my least controversial principle. Whether it’s an HTTP API, a set of functions a module exports, or a web form, leave as little room for error as possible and view ambiguity as your enemy. Provide strong guardrails, clear errors and warnings, and spare yourself from a whole host of bugs in the future.
Since it feels incomplete to write a blog post about software without at least a little bit of code,
here are some examples in Rust demonstrating what I mean. Here’s a well-defined clock module
that allows for unit conversion and answering whether a timestamp is past a given deadline. It makes
the units clear to understand and is hard to misuse.
mod clock {
#[derive(Debug, Copy, Clone)]
pub struct UnixSeconds(pub i64);
#[derive(Debug, Copy, Clone)]
pub struct UnixMillis(pub i64);
pub fn seconds_to_millis(ts: UnixSeconds) -> UnixMillis {
UnixMillis(ts.0 * 1_000)
}
pub fn is_expired(now: UnixSeconds, expires_at: UnixSeconds) -> bool {
now.0 >= expires_at.0
}
}
Meanwhile, the following implementation technically works, but creates ambiguity around what units it expects as input.
mod clock {
pub fn to_millis(ts: i64) -> i64 {
ts * 1_000 // assumes input is seconds
}
pub fn is_expired(now: i64, expires_at: i64) -> bool {
now >= expires_at
}
}
fn main() {
let now_ms = 1_700_000_000_000_i64; // milliseconds since epoch
let expires_at_s = 1_700_000_100_i64; // seconds since epoch
// Bug: comparing millis to seconds compiles but gives an incorrect answer
println!("Expired? {}", clock::is_expired(now_ms, expires_at_s));
// Ambiguous when used - is the input seconds, minutes, milliseconds, or something else?
println!("To millis: {}", clock::to_millis(1_700_000_000));
}
4. Don’t get too caught up in measuring things
I have shipped a lot of highly impactful things over the years, and I can promise you that 99% of the time, I did not care about metrics at all. This discipline we call software engineering is often more art than science, and if you reflect on what goes well and what doesn’t consistently enough, you’ll likely develop an intuition for how to build things that can take you a lot further, at a much faster pace, than a heavy reliance on (likely flawed) data. The systems teams use to measure their software and its users can often be poorly maintained or set up in a rushed manner after an incident when a product manager couldn’t answer something for leadership or an investor, making the quality of the data and intake process questionable.
Business realities, such as deadlines, can trump this principle, but I think it holds up pretty well. The questions of what to build and how to build it can often be a lot easier to answer than one might think.
5. Spend time understanding the people around you and your software
This point might apply more to individuals, smaller projects, or teams that build tools for folks within their organization, but I still hold it near and dear to me. I’ll always get more fulfillment from making someone happy than I will from making 10 imaginary people happy. Pay attention to how people use and talk about your software, and cater to them when you can! Everyone involved will have a better time.
I think that tech workers too often internalize the logic of venture capitalists. By that, I mean an obsession with scaling, and by extension, building for hypothetical users. Are there instances where you’ll get punched in the mouth if you don’t think about scale at the outset? Of course. Years of working on infrastructure has come with plenty of moments where I’m kicking myself for not having better foresight when it came to this database X tipping over or service Y not being able to handle enough concurrent requests.
What motivates these principles
While I would like to think that most of what I said above isn’t that controversial, I know a few principles may raise an eyebrow or two. I can share what motivates them for more context:
- I want to make things that are simple and enjoyable to use.
- It matters a lot more to me to answer to and help the people I actually work with or I know are using the software, versus generic “personas” or “roles”.
- I find that teams that get too caught up in measuring things go slower, think less creatively, and frankly don’t understand data analysis well enough to really back up what they claim to care about.
- If you have a good enough team that cares about what they are building, “rolling your own” should not be so scary.
Some tools of choice
Though only mildly relevant, I enjoy talking about tools too much to stop myself from rambling about them for at least a little bit.
I put a lot of care into my development environment, which is the same for both work and personal projects, and it doesn’t change that often. The way I work in 2026 looks a lot like the way I worked in 2017 for the most part, LLMs showing up on the scene aside.
- OS / Desktop Environment: Fedora running KDE
- Shell: Bash, with a nice theme and minimal Git integration
- Terminal: Kitty, paired with
tmux - Editor: Neovim, with a lot of customization
- LLMs, agents, and the like: I’ve settled on OpenCode
paired with either Codex or Claude depending on which way the AI winds are blowing any given month. OpenCode
configuration is in my
dotfilesrepository, linked above.
In summary - if any of these points strikes a chord, positive or otherwise, I’d love to hear from you in the comments! Or open up my contact form to chat privately if public comment threads aren’t your cup of tea. 🍵
Comments