[Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.]

Assorted thoughts on programming, in particular on writing code for yourself and not for your employer. Nothing here is too surprising and can be found on a variety of sources. These pieces of advice are language-agnostic, at the bottom of the page there are some language-specific resources.

Have fun

Writing software shouldn't be painful. You should never feel you're using the wrong tool for the job, you should never feel like "there must be a better way of doing this, It's so tedious!".

In other words, I believe that trying to keep your coding experience not just barely bearable, but fun, highly contributes to the quality of the code you're creating.

This is often not possible in a professional environment with deadlines and shit, but the gist of it is that the "right language" and the "right way" is whatever makes you feel comfortable and actually willing to write code and maintain it. More guidelines below.

Less software is generally best.

Less to read, less to maintain, less bugs... the list goes on. We've all heard of suckless. I always try to run the simplest solution and write the least code. Avoid overly verbose languages. This does not mean that all verbose languages and bloated software must necessarily suck, just that whenever a simpler alternative is possible I'd always go for it.

I'd much rather run software that I can understand and hack eventually, instead of something incomprehensible to me (Systemd, Xorg, the list goes on). By the same logic, I'd rather write code that's easy to extend and hack.

Use a language that is safer, but suits your needs

If it's a viable, use languages with strong types and safety guarantees, or use the safest constructs in your language of choice. I'm aware that modern C++ has a million of nice and safe features, but I'd rather use a language that was design from the start with safety in mind.

The evidence that statically typed languages are better is lacking, as far as studies go: there is a clear trade-off between the time it takes to develop software with a very constraining type-system and the time it takes to fix the bugs that might arise during runtime in a weakly-typed language. Some suggest to prototype your programs in Python and then slowly re-write them in a compiled, statically typed language.

As far as developing software for your own sake is concerned, I'd much rather have errors pop up during compilation rather than during execution anyway. This is not to say scripting languages do not have a place: they are called scripting languages for a reason, and you can write reasonably fast and safe code in Python too.

And sometimes you're coding because you're making a game or art, and safety or speed are not paramount!

Appendix: Do your error handling correctly.

Try to handle all errors you realistically can, and only to error when it is necessary. If your language supports it, use a construct that forces the caller to handle all errors, like Rust's Result<T>. It's still possible to .unwrap() it but it takes more effort to ignore it than in most languages.

If your programming language supports exceptions, use them for truly "exceptional" circumstances.

Appendix: Make the impossible unrepresentable (or: Parse, don't validate)

Unless you work on kernels, if your code ends up in a state from which recovery is impossible, you can actually abort, crash, or panic. Make sure to print an error message before doing so, if possible.

Better yet, you could make absolutely infeasible to get into that state to begin with: to rephrase it, your program, at any time, can only be in a finite amount of states. Restrict this set of states to the minimal practical one, make invalid states unrepresentable, they simply cannot happen. Don't waste time on checks in your code when you could've halted long before the checks. This blog post by one of my personal heroes explains this point in detail.

You don't necessary need to encode SQL at type level to feel good about your program: sometimes it's very tedious to describe the valid states to the compiler.

Appendix: Tests are like homework

Proper unit testing is immensely effective at detecting bugs over time in a codebase. [TO WRITE]

Appendix: Asserts everywhere!

If your language has a way to assert() for some condition, do it every time you rely on that condition but it is not enforced at the type level or language level.

Don't repeat yourself - DRY principle

This principle can be applied to a wide range of scenarios: writing the same piece of code twice? Make a function. Soon enough you'll have to call it again.

Calling some boilerplate coming from an external library you're using, and doing so in two modules of yours? Wrap the library in a module of your making, and reduce the boilerplate.

Writing documentation and keeping it up-to-date is hard? Use automatically generated documentation, writing only necessary documentation directly on top of the function you're trying to document.

If you need to maintain an API between two languages, don't. Use a code generator, such as Protocol Buffer or similar. Wayland does this. It will save you countless headaches in the future.

Whatever feels like it should belong to a separate module because you could use it somewhere else belongs to a separate module. Laziness in the present could result in much, much more effort in the future. If you feel like "I'll have to do it later anyway", do it now.

Care about efficiency

Space is cheap and computers are fast, yes, but that shouldn't be an excuse to completely disregard any memory and performance optimization. "Early optimization is the source of all evil" my ass. Learn your data structures and use the best fit for the job.

Again, there's a clear trade-off between development speed, convenience, and efficiency (i.e. writing a quick and dirty bash script full of greps and pipes instead of writing a C program unrolling loops manually and god-knows-what).

But whenever it's viable, don't be lazy: write fast software. The user WILL feel the slightest delay. It's the reason why Electron applications often feel clunky: while they're simple and easy to develop, only requiring you to know web-development skills, they're basically ran in a browser when I have an entire operating system that could run programs natively instead. I don't despise Electron apps, I just find unpleasant to use them. And when I write software, I don't want to write programs that are unpleasant to use.

Sometimes it's also hard to accomplish both maximum speed and modularity: breaking programs apart makes each single component easier to swap and hack, but also introduces a overhead. But when you're worrying that breaking code into modules could prevent the compiler from inlining a bunch of function calls in a hot loop, you probably have enough judgement to set code style aside.

Some books I like

Most of this advice can be found in The pragmatic programmer, other books to come here.

top↑ end↓