To New Graduates: Introduction
Author: Alexander Avery
Posted: Wed | Jul 3, 2024
computer-scienceSimple Software
Embarking on a career in software development presents new challenges, goals, and rewards, regardless if you’ve completed any formal software engineering program. I received a bachelors degree in computer science in 2020, and spent months interning during the four-year program. Despite this, the things I came to learn from working full-time have built upon the fundamentals of my college education in ways I could never have predicted. Over the course of the next six posts in this series, I hope to make an impression of how my values and mentality have developed in the four years since completing my degree.
Though the things I focus on span many topics, I would attempt to surmise them with one concept: making simple software.
The Importance of Experience
Despite the personal experience I am planning on sharing, I don’t expect these posts to fully divulge the information that’s inside my mind to yours. If anything, you can combine these posts with your own experiences to come to your own conclusions. My intention is that these writings are detailed enough to allow you to predict the kinds of software I would write. With that in mind, let’s begin.
Collaboration in Software Development
I mentioned earlier that I had spent many months interning during my bachelors degree. Luckily, one thing I was exposed to during this time was the reality of writing code meant to solve a problem. In this way, even if you work entirely alone, software development can be a very social endeavor. You could write software all by yourself, for only yourself, but even still, are you writing machine instructions directly into the memory of your PC? If you aren’t, you are using software written by other people, and probably a lot of it.
It can be rewarding to provide value for someone else, and to produce value if you are a software developer is to make software tools that solve real world problems. One important thing to keep in mind is that not all software that exists solves real problems, in fact, writing bad software can even create problems at the cost of your hard work and time. Always use real world value as your compass, and regularly question if the things you do daily are helping or hurting you in that goal.
Little did I realize that during my internship, I rarely worked on projects with other developers. Usually I would be assigned some small project that could be accomplished over a summer, and I would spend each day writing code, sometimes asking my coworkers for help in solving a bug or deployment issue.
It should be said the difference between working alone and working alongside even one other developer is stark. Assuming your co-workers know at least as much as you do, there are still many hurdles to avoid. They will write code that you depend on and vice versa. If there are differences in your understanding of the problem and goal, you may miss your mark.
An Example
Imagine the following, you and your team need to write a GUI that shows a list of items. The GUI needs to enable deletion of these items in any order. You’re a savvy computer scientist, so you say these items ought to be stored in a tree for easy lookup and deletion. The team agrees, and it’s time to get to work.
You are responsible for writing library code, so you spend a few days implementing a tree of items that you will later pass to a view function provided by your coworker. The time comes to integrate your changes with your coworkers, and you notice the view function is expecting an array of items as an argument, not a tree of items.
You remark:
This isn’t right, we talked about using a tree to delete the items easily in our meeting.
Your coworker replies:
Of course, the array that contains the tree can be passsed right in.
That’s right, you can implement a binary tree with an array as the backing data structure, but that’s not what you had in mind.
You wanted to provide a Tree
struct with a suite of methods for the view code to use: Insert
, Find
, Remove
, etc.
And over the last few days you implemented the tree as such, using pointers as a means of reaching child nodes.
Your coworker thought you would just pass in the backing array of a tree. Upon this assumption, he also spent days developing functions that would insert, find, and remove nodes in this array. Not only have you and your coworker duplicated work, but you’ve made two APIs that are fundamentally incompatible. Reconciling this will take time, you’ll have to consider what code to take into the final product. Will your code be deleted, or will he need to rewrite his code to use your tree methods over his array functions?
Does this mean either of you are bad at writing code? Not necessarily, both of your implementations work independently, the failure is in the fact that the goal was not communicated effectively. Successful communication between developers can take a significant amount of time. Unsuccessful communication takes nearly as long as successful communication, and later takes twice as long when everybody runs to put out fires.
To put it simply, building the right thing takes time. Building the wrong thing also takes time, but produces no value. It takes sharp communication skills to avoid building the wrong thing.
Wherever you work, there will likely be systems in place for task assignments, but don’t be fooled. The onus is still on you to effectively communicate with other developers as you work. If you haven’t read The Mythical Man Month, set aside a few days to read it. It elaborates in far greater detail about how time spent in large engineering groups can be dominated by the time it takes to communicate ideas.
Ever-Changing Technology and APIs
There are many exciting and shiny technologies out there. Depending on your exact field, new libraries and frameworks could be released daily. It is extremely tempting for many to value new tools over current ones, but this can be costly.
There is value in having a consistent suite of tools to work with. The success of Go, for example, is in part due to the Go 1 promise. Interface stability respects the time of your end users because you don’t require them to relearn things for little to no gain.
Imagine that just after you graduate from your computer science program, all computer science concepts are deprecated. Queues, stacks, trees, heaps, arrays, context-free grammars, and lexers have been replaced with “bueues”, “tacks”, “meaps”, “orrays”, “context-ish-grammars”, and “flexers”. What just happened to the value of your degree?
Is it worth it to spend another four years to learn this new stuff? What if those new concepts become deprecated, too? Do these new concepts even improve the field? There might even be a lot of hype around them, but it’s up to you to separate the gold from the fools-gold.
Don’t voluntarily do this to yourself by jumping ship to new technologies, languages, or tools before knowing if there is any real value. Regularly rewriting code bases or swapping database backends without real measurable gain is just paper-shuffling. If you want to improve people’s lives with your code, you will need to learn to make great APIs and commit to supporting them for a long time.
Performance Aware Programming
Modern hardware is unfathomably fast, so it may be surprising that it’s still easy to write really slow code. If you can write performant code, you will have a positive (but currently rare) impact on your end users.
When your product is shipped to users, or deployed several times in some cloud architecture, inefficiencies are multiplied. It pays to be aware of not only the impact on cloud costs or end users, but also how to make your programs performant. As an exercise, consider how an operation that takes five seconds to run may affect dozens of users in the course of a week. Five seconds may not feel significant in testing, but your code could be run many hundreds of times in a week. If you could instead write code that performs the same operation in under one second, the downstream effects would be incredibly valuable.
In order to write performant software, you should be aware of the hardware your code will run on, and know how to identify causes of performance problems.
Small Sharp Tools
Whatever tools you use, or are required to use, ensure that you leverage them to the best of your ability. While I cannot predict the text editor or IDE you use, I am willing to bet you will use Git for version control, and will have access to a *nix machine.
With this in mind, I want to impart some lesser known capabilities of Git and other *nix programs that will assist you greatly in any aspect of development.
Git
The term “version control” doesn’t begin to explain the capabilities Git has to offer you.
If you are using Git for the first time, it may feel as though git-add
git-commit
and git-push
just get in your way.
Many people new to Git desire to have a platform like Google Drive that provides multiple users with a shared live-editing environment.
If you learn just a portion of the most useful Git features, you may soon find yourself using Git for more than just source code version control. I personally use Git for plain text accounting, and managing travel itineraries written in Groff.
Debugging
If you use an IDE for programming, you may have used your IDE’s integrated debugger. In this final post, I want to present Unix as an IDE.
When you are doing work on remote servers, it is invaluable to know how to use the POSIX and GNU programs that are likely available. Many of these programs, written decades ago, can integrate seamlessly, and can even be extended to serve your particular needs. In my decade of programming, Unix has become a personal favorite due to its small, sharp tools that interface with each other and with me.