College prepared me to enter any kind of software job I would want to enter after graduating. I had the tools I needed to get a job in web design, embedded systems, or anywhere in between. I mostly wrote code in object-oriented programming languages, but I had a occasions where I wrote code in F# (a functional programming language), assembly, and got to try aspect-oriented programming in Java. I had many hundreds of hours of debugging code under my belt by the time I graduated, which made me confident that I could find and fix any bug in any program.
There is an enormous range of topics that professors need to teach students in the 4-5 years that the students are there. They can’t go over everything that you may encounter in any software job. Plus, technology is changing so quickly it’s hard to keep up. However, after working in the industry for 4 years now, I have some suggestions for new graduates and people who are just starting their first software job. These are topics or tools that where either not covered at all, or in my opinion not covered thoroughly enough when I was in college. I believe these topics and tools are incredibly valuable in most software jobs.
Back in school, programming assignments would usually consist of getting the specifications for the program to be written (the sample output being the most important part), writing the program, submitting it, then forgetting about it. Doing this for 4-5 years may make you think that when you’re working at your job, you just need to worry about coding. You’ll need to add new code for new features, or modify existing code to change features or fix bugs. But there are so many other activities you’ll be involved in, or at least need to be aware of, as a developer. These activities include, but are not limited to the following.
- Updating a test machine with the latest build.
- Deploy the latest build to a customer, including any configuration updates.
- Versioning and storing all the builds in the correct spot. All versions should be easily accessible. It should be easy to see if a build is a snapshot or release. It should be easy to figure out which build introduced a new feature (or bug).
- Creating new virtual machines for various people.
- Regression testing. When you check in new code you probably tested the new feature, but you probably didn’t test all of the existing features to make sure you didn’t break them. Or what if code that another developer checks in breaks your new feature?
The above activities are also prone to error. A developer may accidentally update the wrong machine, or not update it correctly. Configurations are easy to get wrong. It’s easy to forget to test something. This is where devops comes in.
Devops is a set of practices with the goal of delivering software that is high in quality, as fast as possible. In devops, we want to automate as many processes as possible. All of the above activities I mentioned should be automated as much as possible. For example, when a developer checks in new code, a build server should automatically make sure the code compiles, and then run a regression testing suite. If any of the tests fail, an email can be sent to the developer telling them that their new code broke something. If all the tests pass, the new build can be automatically versioned and uploaded to an artifact repository. Then the build could be deployed to a test box automatically.
In devops, we use build servers and scripts to automate as much of the development process as we can. Ideally, developers can just focus on coding and have everything else be automated. Other processes that could be automated include the following.
- Run integration tests nightly (because integration tests can take a long time)
- Run static code analysis, using SonarQube for example.
- Spinning up a new virtual machine.
- Generating release notes.
- Only allowing commits to your main branch (master) if all tests are passing.
Devops was a word that I don’t remember ever being brought up in college. Neither was continuous integration, continuous delivery (CICD). The words “build system” may have been mentioned once or twice, but I never understood what it meant. Now that I’ve had a couple real software jobs, I understand how important it is. I wish that in school we had the opportunity to set up a simple build server. For example, for one of our projects we could have set up a Jenkins server and created a simple job that compiles a simple program and runs a few simple tests whenever there is a new commit.
Design patterns are solutions to common problems in software design. Observer, dependency injection, visitor, and factory are all examples of design patterns.
Design patterns were covered briefly in my schooling. I can remember two occasions. On the first occasion my professor just had a slide show where he showed the class diagrams for a bunch of different design patterns, which didn’t help me understand at all how to apply them. They just seemed like vague, abstract concepts when looking at the class diagrams. On the other occasion we were creating a program that required an undo/redo feature. My professor recommended we look into the command pattern for implementing it. I did, and it made implementing undo/redo very easy. I got a taste of the power they can provide, but it wasn’t clear to me how to apply other patterns.
It wasn’t until I started programming my game that I started to understand how powerful design patterns were. Games, even ones as simple as the one I made, are complex. At some point it became nearly impossible to add more features to my game, because I’d have to change too much code. Then I found out about the entity component system pattern. Then I read Game Programming Patterns by Robert Nystrom. I learned how several design patterns could be applied to the my game code. I ended up using a mix between the entity component system and observer pattern to make it easy to add new abilities to my game characters. I used the state pattern to make it easy to change an object’s behavior. I used dependency injection to make it easier to test the code.
You can’t teach design patterns by just showing class diagrams. For me, it took many examples to understand how to apply them. It took running into problems with my game before I understood how powerful they are. Back in school, I wish there were assignments where I couldn’t just code the program any way I wanted to, but required a specific design pattern to be used (one that makes sense for the assignment).
I remember one assignment where it was a requirement to write tests (in C#). But we weren’t taught how to write tests, or write testable code, and I didn’t know about any C# testing frameworks at the time, so it was a challenge. There should’ve been a whole class on writing tests, and for each class after that writing unit tests for assignments should have been mandatory.
Writing tests and testable code is, I would say, more important than anything else. When you get a software job, if the system you’re working in doesn’t have tests and testable code, then it will slowly accumulate bugs. Then each time you fix a bug, you introduce 2 more somewhere else in the system. It becomes harder and harder to test, and you’ll become scared of making any changes. Development becomes more and more costly and slows to a snail’s pace.
If the system your working in has automated tests for every line of code and every feature, then maintaining and adding new features to the system becomes monumentally easier. Whenever you add a new feature, or modify an existing one, you can run the tests to make sure you didn’t break anything. You can catch bugs before you commit code (the earlier bugs are caught, the cheaper they are to fix).
Testable code is always better than code that isn’t testable. This is because for code to be testable, it needs to be clean. It needs to be loosely coupled, highly cohesive, follow the single responsibility principle, follow the open-close principle, and probably use dependency injection, among other coding principles. It should be easy to write tests for code that is clean.
Taking the extra time to write testable code and tests for that code will end up saving you much more time and headaches overall. To get started, I recommend reading the following two books.
Package Management Systems
Back in school, I wasn’t aware of how easy it is to install open-source libraries into my project using package management systems. In the previous section, I mentioned that I had to write tests but didn’t know about any C# testing frameworks. It would have been great to use Nuget to easily install Xunit and use it to write my tests.
Package managers allow you to easily install, update, configure, and remove 3rd party libraries for your own projects. For .Net there is Nuget. For Ruby there’s gems. For Java there is Gradle (among others). Most languages have package managers. If you need a testing framework, IoC container, or an ORM, just to name a few, you’ll find many to choose from when using package managers.
Back in school we used Subversion. At my first job I was exposed to Git and can say I’ll never go back. I’ve also used TFS, but Git is by far my preferred source control system. The thing that makes Git stand above all the rest for me is how easy it makes branching and merging. The ease of branching and merging makes many different branching strategies possible. It’s also distributive, meaning you can make commits offline. It’s runs using the command line, but there are many GUIs available for it (I use Git Extensions).
To get started, I recommend this guide.
Like I mentioned before, college prepared me to be able to enter any software job. It gave me the basics I needed to be able to learn whatever else I would need for the job. But if the above concepts and tools were included more in my classes, especially testing, I believe I would’ve been even more prepared.