Over the past little while, I’ve compiled a list of “commandments”: things you must and must not do as a modern developer. Let’s walk through five of them and discuss why you should adopt them for yourself and your teams too.
1. Monorepos: Just Don’t
If you’re not familiar with the concept of a monorepo (I envy you) then let me explain: instead of having multiple source-code repositories for your applications, the monorepo concept places everything in a single repository.
This can make contributing to multiple projects easier, but it comes at a cost: you must use subversion, you cannot use Git. Git, for all its strengths, does not support sparse checkouts like subversion.
Sparse checkouts allow you to check out individual directories of a larger tree instead of the whole tree as Git does. This means you can have multiple people or teams work on individual parts of a tree without overlap.
You cannot to this with Git and therefore should use individual repositories for discrete applications when using Git as your source control.
2. Problem First, Then Solution.
But we run into trouble when those preferences become requirements; a lens through which every problem is viewed that cannot be deviated from. And let’s not be deceived, this is more than just an individual vice, organizations are guilty of this too.
Lots of companies mandate certain technologies, libraries, or tools, often with little thought or input from the “boots on the ground”, developers and operations engineers who have to actually use or implement these technologies.
This is part of my long-standing gripe with enterprise architecture groups and their God-like powers over the mere mortals who actually write the code.
Oftentimes, it’s the architecture group that decides that the company is going to use a certain technology or product — Kubernetes, OpenShift, AWS, etc. — without fully understanding the problems inside the organization and what these technologies were designed to solve.
I saw this personally during my time at Capital One where our architecture group decided that we were going to be a Kubernetes company but didn’t have any real sense of what that meant to those of us who had to actually develop and implement the systems and tooling around it, or the applications that would run on it.
And it was often architecture (or their malnourished stepbrothers, enterprise security) that were the cause of a lot of the roadblocks to them getting what they wanted.
Had they — both architecture and security — understood the problems that needed to be solved first before deciding what tools to use second, then things might have gone very differently and most likely a whole lot smoother.
3. Ask Questions
It sounds so easy. So simple. So childish. Yet, so hard. Don’t understand something? Ask questions. Want to know why something is the way it is? Ask questions. Want to know where the project is headed? Ask questions.
Just because you ask doesn’t mean you are going to get the answer you want, or any answer at all, but if you don’t ask questions then you’ll never find out.
One of the best things to do after coming to a new team or starting a new job is to ask all the questions. Pulling the FNG card is like having a “get out of looking stupid” card for the first little while.
Asking questions that start with: “Hey, I’m new to all of this, so let me ask what might be a dumb question…” is an awesome way to find out things you want to know but also challenge the status quo.
You’d be surprised to find out how many organizations are doing things a certain way “because reasons”. Usually, it’s because someone set it up that way a while ago and no one bothered to go back and fix it.
By asking questions, challenging assumptions, and digging for information, we make our teams, our groups, ourselves, and our lives better. I’ve been able to cut out entire layers from infrastructures from asking questions like this.
Who knows what you’ll be able to trim out.
4. Square Pegs Don’t Go in Round Holes
Like a lot of good things — making love, team chemistry, precision-threaded machine screws — when it’s good it feels effortless. Our lives are filled with feedback, explicit or not.
The way your keyboard feels under your fingers as you type, the little vibration “click” your phone does when you press a flat imaginary button, the way my lactose-intolerant stomach revolts every time I decide that having ice cream is 100% worth it; these are all forms of feedback.
They let us know when something is going well, normal, or going very, very badly, and it’s the same for literally everything. We’ve all been there before, working on a project, that nagging feeling in the pit of our stomach that keeps telling us that we should change databases to better support our data model.
That, instead of writing a lot of fragile data transformation code, you could do a lot of this in-database if you just used a relational database and an ORM. Or, after finding yourself on a new team or in a new job and you just don’t get along with your peers for some reason.
It’s not that you don’t like them or they don’t like you, some personalities just work better together than others. Don’t force it. Find the better solution and go with that.
Talk to your manager about changing teams. Find an ORM and get to work. Stop what you’re doing and do the thing that’s going to make it effortless. Leave the square-peg/round-hole problems to the NASA nerds.
5. Use the Best Tool for the Job (Unless Its Java)
I’m probably going to get a lot of hate for saying this, but I don’t see a reason to be using Java in today’s industry.
Java does have some differentiators against its competition, I won’t deny that, but those differentiators don’t really apply to today’s engineering environments. Here are some pros for using Java:
- It can run anywhere.
- Automatic memory management (with its garbage collector).
- Extensive community and frameworks/libraries/plugins for the JVM stack.
Let’s talk reality for a minute: how many software shops that use Java do you see writing a single codebase with the intent of running it on multiple architectures, operating systems, etc.? Not the majority, at least.
And today, Java isn’t nearly as unique in the memory management space, either. Both Go and Rust have some sort of garbage collection, Python uses reference counting, and many other languages do as well.
And Java is by far not the only language with large active communities around it. Rust and Python have incredibly active and helpful communities, with Go’s community ramping every day.
But the other trade-offs you make with Java, at least in my opinion, aren’t worth it. Because Java relies on the JVM, there is an automatic size cost incurred with every Java application.
This might not be much of a consideration when talking about servers that have gigabytes of free space — a couple of hundred MB’s aren’t much — but in a highly containerized world a few hundred MB is astronomical. (Note that Python suffers from this downside as well.)
With compiled, statically-linked languages like Go and Rust (and others), you can have very small, very lean containers that often have a single binary in them, with sizes as small as 4 MB.
This is important especially for large organizations where network throughput is a premium, downloading a new container that’s 400 MB or 5 MB is an easy choice to make.
Also, because of the JVM and that Java is JIT-compiled, there is a performance cost to running Java code.
For low-latency, high-throughput applications, or scenarios where bin packing a server is extremely important, losing performance to the overhead of translating bytecode to system calls just isn’t worth it.
All of this is why it’s important to use the right tool for the job at hand.
You don’t want to use BASIC for landing someone on the moon, and you don’t want to use Java for high-performance computing — find the solution that matches the problem you’re trying to solve.
Know of anything I missed? Have ideas for more rules? Let me know in the comments below! I love to see your feedback and engage with everyone’s thoughts on the articles.