Common mistakes new developers make
I've been a new dev, you've been a new dev, we've all been new devs at some point. Here are some things that new devs often do, and what you should do instead.
Overzealous use of exception handling
Perhaps this is an AI thing, but new developers love their try/except blocks. Unfortunately, this is usually implemented in a way that's worse that just not using exception handling at all, because the exception isn't actually handled in a way that makes sense for the code being executed. This often manifests in an exception block that just prints the exception message and continues.
Exception handling is great if there's a valid reason for not throwing an error immediately. Perhaps you're making an API call and you can expect transient networking issues that cause latency and timeouts, and you implement an exception handler that checks for that specific error code and retries. Great! Perhaps you have an ETL that makes a series of SQL queries to pull data for transformation; a try/except might make sense here if you want to log an error in a specific way and perhaps gracefully exit the process (close other connections, clean up files on disk, whatever). That's also great. But that is often not what new developers write. This is pretty common:
try:
etl.pull_data()
except Exception as ex:
logger.error(ex)
That is, just logging the error and continuing. An error pulling data for an ETL is almost certainly existential for the ETL. We can't just log the error and continue.
Another one I see that coding agents love to implement is specific exception handling blocks for
every conceivable exception. Instead of a general try/except, I'll see five difference exception
blocks. For example, when making an API call using requests, I might see except HttpError, except IOError, except ConnectionError, and so on.
Why? Are you really going to handle an InvalidHeader error and an
InvalidURL error differently? Seems unlikely.
The point here is to think carefully about the behaviour you want to occur on an error, rather than just blindly implementing a try/except because it's "best practice".
Overzealous use of dependencies
I remember being a new developer writing R code (a language that's very useful in a statistical/data science context). Everyone loved to say things like "the great thing about R is the large ecosystem of third-party packages" that you can use for various statistical analyses and what have you. I suppose if you're comparing R to something like SAS, then that might make sense, but third-party package ecosystems are not a novel thing. Anyway.
That sort of sentiment is not exclusive to R users, but it's particularly prevalent there. The R package ecosystem is very simple; unlike Python, where there are dozens of ways to package code, R has basically one way, and packages are mostly hosted in one place, CRAN. There's also usually no virtual environment shenanigans, so a single library supports all packages for a given version. I think these two things contribute to a tendency within the R community to include package dependencies on the smallest whim.
To be fair, this isn't just an R thing. It's an issue in Python and JavaScript and pretty much any similar language with a similar public package manager. But the point is - new developers love to use dependencies, and I think, a little too much.
The advantages are obvious - don't reinvent the wheel, someone else has already done the work. But the disadvantages can be trememdous. The main two are security and maintenance.
- Security: Supply-chain attacks are so prevalent nowadays. The more dependencies you have, the more likely you are to pull in a package that has been compromised.
- Maintenance: The more packages you rely on, the higher the likelihood that you'll have to spend a considerable amount of time resolving package conflicts and breaking changes.
As a new developer, the trade-off seems very one-sided: Adding this one dependency solves my problem immediately. The downsides are vague and may only appear several years from now; but if you care about whoever has to maintain the code in the future, you should really consider whether you can instead roll your own function instead of importing a package that does it for you.
Bonus tip: When considering whether to add a dependency, please, at the very least, check the package's source code repository and package index pages. So many packages have been abandoned but are still available to download; these ones are more likely to end up with security issues or conflicts with other packages. If the maintainer hasn't responded to any issues or pull requests for five years, it's probably not worth using that package.
Overzealous use of abstractions
AKA, thinking too far ahead. People love to design for future use cases that may or may not eventuate. You're writing some code that parses a data file and pulls out various fields into a standardised format. The stakeholder has speculated that they might want to handle multiple data formats at some indeterminate time in the future. In my opinion, the correct approach in most instances is to design a system that only handles the current data format.
This might just look like a few functions. Maybe it's a Python class. But unless you know for a fact that you will need to support new data formats in the near future, I don't think it should be an extremely generic class. Because, chances are, you'll never write the code that actually implements those new data formats, and now you're stuck with this generic, abstracted monstrosity that's much harder to reason about than a more targeted piece of code.
So: Write code for your current requirements, not nebulous future requirements.
Commenting the what instead of the why
# Define the number of retries
NUMBER_RETRIES: int = 3
This is commenting the what instead of the why. And it's completely pointless. Anyone can tell that NUMBER_RETRIES defines the number of retries.
# We occasionally see timeout errors if the server is overloaded; these are almost all resolved within a few retry attempts.
NUMBER_RETRIES: int = 3
This is commenting the why, and it's much more useful. Here are a couple more real examples of commenting the what that I've seen in the wild:
border: none; # don't display the border
# define constants
NUMBER_RETRIES: int = 3
...
I suppose this does depend on you writing good variable names, but of course you do that, right?
Thinking that the job is mostly writing code
I don't think much needs to be said on this. If you're getting into software development because you love writing code, that's great - but you're probably going to spend a lot more time reviewing code than writing it.