With the increasing use of automation and overlap between roles, many network engineers will end up writing software, or likely already have. If you write a bash script to automate something, you are a developer and that means you can benefit from learning how to do it right.
I have seen plenty of cases where some person wrote some code ages ago, left the organization — leaving nobody knowing how the code works — and it became a business-critical component, that nobody dares to touch.
Good development practices will reduce dependencies on poorly written code, make it easier to adapt to changes, and make you less dependent on individual people. Here are few places to get started. I also covered all of these points in more detail in my RIPE 79 presentation.
Use version control
The first step towards better coding practices is to use version control. It allows you to track changes to the code over time, which is essential when someone needs to do some software archeology.
Common choices today are git or mercurial. Older choices, that I do not recommend, are subversion, CVS or RCS.
Git, mercurial and RCS can be used offline, so you don’t even need a network connection or any kind of server. If you don’t know what to learn, learn Git — it’s wide use in open source makes it an incredibly useful skill.
If you use Git, you can consider hosting your code on GitHub, as it gives you a lot of integration options. There are other hosting platforms as well, such as GitLab, and they all allow you to keep your code private.
Git only requires you to know a few commands to get started:
#set the right author name/email (once) git config --global user.name "full name" git config --global user.email "email@example.com" git init # make a git repo git add . # add all current files git commit -a # commit all changes (editor opens) git push # if you want to push changes (not required)
Now you’re using version control! You can continue to use Git add/commit/push when you make changes. Just remember to write commit messages that are useful in the future.
Tools such as Git have a lot of useful but complex other features, like rebasing, commit amending, and undoing commits. You don’t need to know these when you get started. The above is all you need to have a project in version control.
Second, and perhaps most important, is to write tests. Having tests will save time in development, reduce the amount of errors, and give you confidence with making changes.
Big projects sometimes have complex test suites — one of my current projects, IRRDv4, has almost 5,000 lines of code just for tests. But you can start small, for example, with a single integration test. Most languages have built-in or third-party libraries that help you to build a test suite. You can even write tests for your bash scripts.
Integration tests essentially cover how the system works as a whole. This is opposed to unit tests, which test smaller individual parts. You may not need unit tests to start with. In larger projects, the distinction is helpful because in small fast tests, it’s cheap to run through lots of different scenarios, and only test the fundamentals in integration tests. Note: not everyone uses exactly the same definitions for test scope.
Tests should cover weird scenariosA classic pitfall in testing is to only cover the ‘happy path’ — the scenario that occurs if everything goes well. For IRRDv4, the tests I wrote included:
- What if someone submits a PGP signed message but includes additional content that is not part of the signed content?
- What if someone submits an inetnum object, which can only have IPv4 data, but the key mixes IPv4 and IPv6 addresses?
- What feedback does the user get if the parser encounters an internal error somehow, and what log messages are generated?
If you discover a bug, write a failing test for it, then fix it. You can have more confidence that you have fixed the bug, and you can be sure it can’t reoccur in the same way.
In small projects and scripts, a single test may be all you need. Once things grow, you may need a bit more work to design your tests. But even a single test can make an enormous difference. For IRRDv4, my 10,000-line project that was deployed early this year, only two actual bugs were found after deploying into production, in part because I wrote rigorous tests.
Work on readability
Most code is read more often than it is written, so a little extra time to improve it while writing will save you and others time in the end.
In general, ask yourself how clear this code will be in a year to yourself or someone else, even if you think this script will only be used for a few months. In the last decade of doing this work, I’ve never seen “it’s just for a few months” hold up.
A good example is one letter variable names. These are absolutely awful — recently I spent half an hour trying to decode how something worked, because someone had used multiple one letter variable names without context, with the variables’ meanings changing at different places in the same function. A few minutes of work when it was written would have improved this.
Software engineers can have differing opinions about comments in code. I like to use the rule that comments are useful for the how or why, but not the what. For example, explaining that some unusual behaviour is needed to work around a browser bug can be very helpful. But if I find myself wanting to comment what something does, I should probably make the code clearer, which is possible in almost all cases.
In software engineering, refactoring means to restructure something without changing the behaviour. Sometimes you do it because you made a bad decision in the past, or because business requirements changed. It happens in short cycles, where I review and often slightly refactor all code right after writing it, but also happens in longer cycles, when you are expanding to support new features years later.
Refactoring is an essential part of the lifecycle of software, exponentially so as software gets larger or more complex. As an example, in IRRDv4 I chose a framework to handle process management and socket handling, and it turns out it’ll create problems with future development. I’m going to have to take that out and replace it with something new, initially having the same functionality.
Again, tests are essential for refactoring, because you can only make large internal changes if you are not afraid of changing things. This is another reasons why tests are a hard requirement for me when writing code — the need for refactoring is inevitable. And especially with larger refactoring projects, things can break in unexpected ways in unexpected places — which is not a problem, as long as you know.
Don’t get attached
Being able to refactor also means you can’t get too attached to your own code. I often see this in newer developers; people being too attached results in seeing code like this:
# XXX We don't use this function anymore # <300 line function from 4 years ago>
# I think this is broken so I've disabled it # <10 lines of gibberish with syntax errors>
Nobody will ever benefit from leaving things like that in the code.
Recently I worked for two full days on a feature and then threw everything away because my approach was entirely wrong, and I got stuck. That’s not fun, but on the way, I did realize what was the right approach, and this is a normal part of writing code — sometimes this happens a year after you wrote the original code.
All software needs documentation. Even the tiny script that generates some statistics, or just translates one data format to another. Key questions that the documentation should answer are:
- What does this do?
- What problem does it solve? Why did we write it?
- Who wrote it?
- How does it interact with other components?
- What are the known limitations?
- Is there more documentation, and if so, where?
A good place to start, which is often enough for tiny projects, is a README that answers those questions. If you want to learn more about writing good documentation, I recommend watching ‘Docs or it didn’t happen‘.
Know your limitations
Even with all these tips, if you have limited experience or mentoring, there are limits to what projects you can execute successfully. In the same way that I have enough network engineering experience to set up BGP peering, if I were in charge of a large network everyone would have a bad time.
Recently, a client reported a bug and submitted a patch to fix it. The patch changed one line in one file. It fixed the bug in the right place, in the best way, and was entirely correct. But the fix I ended up applying changed 17 lines in 3 files. I looked for other places where the same issue may exist and found one other potential issue. Because it was a bug, I felt it should have a test to ensure the correct behaviour. And as its behaviour had changed, that required a sentence in the documentation.
The key to these kinds of differences — that show even in a very simple patch — is experience.
There is more to this than I can cover in one post, but we’ve covered important fundamentals, which I think will give you a good start. If you have more questions about any of these tips, or other questions, feel free to contact me.
Sasha is an independent Python developer and community manager from Amsterdam, Netherlands.
The views expressed by the authors of this blog are their own and do not necessarily reflect the views of APNIC. Please note a Code of Conduct applies to this blog.