There's a a lot of information that gets crammed into a 4-year (let's be honest, 5-year) computer science degree program. You might've started out like me with no programming experience, learning your first line of code on Day 1 of your degree program. Then you're thrown into the gauntlet, learning data structures, pointer arithmetic, algorithms, discrete math, operating systems, compilers, system architectures, and coming out on the other end a dizzy, pimply-faced 20-something fully ready to enter the workforce, thinking that you're going to use everything you learned every day.
But then when you start working on a team in the real world, you realize there's a lot that school didn't quite prepare you for. You've inherited a mountain of someone else's code, and it's not neatly documented like your homework assignments were. Instead of a working on a standalone Python or Java file to turn in for a grade, you're now working on some remote, behemoth of a system with many pieces of integrated software that you don't fully understand, made by different teams that you've never met, and you're afraid to touch any of it or it will come crashing down on you and you'll be fired the next day.
Okay, that was a little dramatic, but working in the software industry is different than working on a degree program. Although my 4 years at UC San Diego (summer school, baby!) taught me the foundations of computer science, I've learned far more about software engineering in the 11 years I've been with my current employer. And while there's plenty of tried and true "Best Practices" articles out there, there are several principles that I try to go by that have repeatedly come up for me or I've observed in other employees and aren't talked about quite as much as say, DRY (Don't Repeat Yourself) and TDD (Test Driven Development).
They're not best practices; they're just good things to know.
When you first start working at a new company or new project, there's a lot of upfront overhead/administrative work that needs to happen before you can contribute to the team in a meaningful way. In my field, forms have to be submitted, accounts have to be created, certifications might have to be earned, documentation needs to be read, and most importantly, your local development environment needs to be set up. All this could take weeks to be fully completed, and during that time, while you're still a potential asset to the team, you're still an investment that hasn't started paying off yet.
Some of those tasks can be completed sooner. Maybe while you're earning that certification, you can still set up your development environment. Maybe even though you don't have an account yet on the issue tracker (fresh college graduate Me: "What's an issue tracker?"), you can still browse the live system or the code base. How long until you can get a replicable environment set up on your local machine where you can make one tiny change (e.g. change some string) and see that it works? The sooner you can do that, the sooner you can start contributing. Sure, you can instead first read every line of documentation and browse the entire codebase, but your first task is going to be some very small bugfix or basic change that likely only has to do with one part of the application. Unless you're being brought in to re-architect the entire system, you probably don't need to understand every single component of it before you start writing any code.
On some projects, you might be provisioned a development environment such as through a set of containers. But more often, you're given a set of instructions on how to set up your development environment written by another team member. This means you might have a little bit of leeway in how you do it.
The most important thing is to make sure your development environment mimics the production environment as closely as possible. Every difference between your local environment and the production environment leaves room for an untested condition that won't rear it's ugly head until it's already been put into production. Another good practice is to update the dev setup documentation as you encounter differences and gotchas. Paths, versions, and steps may have changed since those instructions were first written, and by updating the documentation you'd be saving future developers the time and energy of figuring out those differences again for themselves.
It's worth putting in extra effort to reduce the amount of time it takes for you to go from making a code change to testing that change. I call this your turnaround time. Working on a bug or feature might require locally repeating this change/test cycle hundreds of times. You change it, you test it. You change it again, test it again. In fact at the end of the day, developing software is really just a matter of changing stuff and seeing what happens.
The Practical DevNo book or teacher can beat good old fashion poking around.14:27 PM - 13 Apr 2016
If it takes 10-15 minutes to see what happened after changing something, not only is your progress being severely hampered, but in that 10-15 minutes you might have even forgotten what it was that you were trying to test! (GWT without dev mode, good riddance). Now multiply this by ten, twenty, thirty times and you're looking at weeks to complete a task that would normally take days.
Depending on the framework you're working with, your turnaround time might be longer or shorter than others. With a lot of front-end development these days, testing is instantaneous and doesn't even require refreshing the browser. If you're coding with GWT or deploying webapps to some remote server however, there could be some downtime or extra steps in the process. If there's any little things you can do to speed this process up, it's worth it in the long run to expend the extra effort up front. Entering the same commands over and over? Make it a script. Having to share a remote testbed with other developers? Replicate it on your own development machine instead.
There's a term used in the software industry known affectionately as your "truck factor". It's the number of people on your team who'd have to be hit with a truck before your project goes under. The truth is, people come and go all the time on a team. It's usually not because they got hit by a truck, or spontaneously combusted, but something a little more boring like they just changed teams or companies.
I try to operate as if my work computer could break or be seized at any time, and this means pushing code, personal notes, documentation, and anything else that might be useful to others off my machine and into a cloud repository or collaboration tool as often as possible.
I also try to operate as if I could be put on a different team at any time, or break (another) limb and be stuck in the hospital without the ability to work for a few weeks. Is there some important tribal knowledge about your project that only you know? Get it written down somewhere where your team members can read it. It's a little morbid, but try to think to yourself "if I were to get hit by a bus today, how screwed would my team members be without me?" I mean, they will of course be impacted a little bit, but they shouldn't be totally hosed because of some work that only you could do. (And hopefully they wouldn't be better off without you!)
So remember that mountain of code you inherited? You're working on fixing part of it, or adding something to it. There's a block of code you don't quite understand, so you mouse over it in your IDE to see the author's commit message, hoping to get some morsel of information or context to explain its purpose. And this is what you read:
John Smith, 8 months ago: "Saving work"
Excuse me, what? "Saving work"?! What exactly do you think a commit message is for, Mr. Smith? Is version control like your personal Save button? Not only is this unhelpful to the reader, but it means you're not committing code in a modular way where each commit represents some singular chunk of related effort, but instead is just a random snapshot in time on your computer. It's understandable to want to push your work off your computer each day (especially if you're following the "explode at any time" rule above), but at least before merging this code, you can take some time to squash your changes into more meaningful commits.
Another gem is the ol' "Initial commit with changes". You may see this more often on newer repositories, and while it makes some sense for the very first commit into a brand new repository, it doesn't need to be there for the first commit of every feature branch. Instead, describe what you changed. Summarize what your code will do (if merged) in at least one complete sentence.
John Smith, 8 months ago: "Remove extraneous call to getPreviousEntry() to prevent duplicate rows from occasionally appearing in the Dashboard table"
Now that's more like it!
Along the same lines as writing commit messages with others in mind, writing meaningful log messages in your code is even more important.
The audience of your server-side logging is likely going to be someone you've never met before. It's possible they don't even work for your company yet, or ever will. When they see
ERROR [com.acme.TransmitUpdater]: Call to getUpdate() failed to complete successfully.
you are guaranteed to be getting a phone call or email to explain what it means. Swallowing exceptions is another surefire way to leave the heirs of your software completely in the dark when they need the most help.
Pretend you are writing your logging statements for you, 20 years in the future. You've woken up from a coma, you don't remember a thing about this software but you're now forced to debug it on a live system in the field with no phone or internet nearby. What sort of logging message would be helpful to this unfortunately cursed version of yourself?
ERROR [com.acme.TransmitUpdater]: Unable to retrieve the latest update. Provided date was
timestamp. Check if date-service is running.
Wow, now I even have a suggestion of what to check! I'll be out of this nightmarish debugging situation in no time.
There's a common practice among software engineering (and engineering in general) that when in doubt, just do what the previous iteration did. Designing a new spec? Just start with the previous one as a baseline. But what if some things in the previous iteration were wrong? Instead of carrying these mistakes forward into perpetuity, think about how YOU would design it if given a clean slate. It might be your only chance to correct something that's been burdening your project or customers for years. Take the opportunity now to make it right going forward.
There's a community on Stack Exchange called Code Golf where users post coding challenges in which the top answers are the ones that solve the puzzle in the smallest amount of code. Here's an example solution on Code Golf in the programming language Jelly for writing a program that outputs
Stack Exchange Chat - The Nineteenth Byte, and also prints
Stack Exchange Chat when the nineteenth byte of the source code is removed:
“ÆçƲBnƥẈṛⱮ_ỴȷOṘỵḊĊ»»ḣ19$ (24 bytes total)
print("Stack",(s="X"&&" - The Nineteenth Byte","Exchange Chat"+s)) (66 bytes total)
Alright, there's some actual words in there at least.
While writing code in this way completely sacrifices readability, the overall concept being promoted here is worthwhile: do it with less.
Sometimes on your first iteration of writing a new feature, you might put in some extraneous code because you're just trying to get something working. But after you get it working, go back and see what was really necessary and what turned out to not be necessary. Delete that extra code. You just want the minimum amount of code necessary to get the job done. Extra code that doesn't do anything useful is just adding to your technical debt. New people who read your code will be afraid to delete it too, because "maybe it does something important that I don't know about." And so now that pointless code is in there forever because no one is brave enough to delete it.
While this sort of strayed from the original topic of code golf puzzles, deleting unnecessary code not only feels good, it's good for you and your team, too 😉.
At the top of every file of automatically generated code should be the words "Generated code, DO NOT EDIT -- Make changes in /this/file instead". When modifying generated code by hand, you're saying "I want this change to be temporary. It is not important to keep around and I am fine if it gets reverted later today". If that's true, then knock yourself out! If it's not, then make the change instead to the code that generates the file you're editing. It may seem like common sense, but not always the case!
Occasionally, changes end up getting made in production environments that -- for one reason or another -- weren't necessary in the development environment (see: "Make Your Dev Environment Mimic Production" to avoid this as much as possible). If these changes were necessary, they should always be rolled back into source control, and then re-tested from there.
History repeats itself, and software engineering is no exception. Problems and errors that you encounter today will likely come back to bite you again in the future. Being able to remind yourself how you previously solved the issue can save hours of debugging and problem solving that you've already done in the past. Keeping a daily log of work accomplished, errors encountered, problems solved, and meetings attended will not only enable you to avoid repeating similar work, but also makes it easier to report your status to management in a more detailed manner without having to try to remember what you did that day/month. I like to keep my log in a git repository of Markdown files numbered for each day, and viewing/editing them in my IDE enables me to quickly search for keywords and errors as they come up. It's an extra process that you might find cumbersome at first, but it's well worth it once you can get into the habit.
What do you think of these software engineering practices? Do any of them resonate with you more than others? Do any of them not make sense or go against your own practices? Tell me what an idiot I am in the comments! 👇