My Experience with Web Development from a Systems Programming Perspective
In this blog post, I’ll describe my personal experience learning about the modern web development ecosystem (programming languages, frameworks, programming paradigms, tooling, etc.) and compare it to my experience doing systems programming in languages like C, C++, Rust, and Go. If you’re a systems programming developer and you’re interested in switching to modern web development, or if you’re a web developer with curiosity about systems programming, this article may be of interest.
From Systems Programming to Web Development
It’s common in many dynamic organizations that engineers are free to explore technologies and programming languages. Taking the time to familiarize yourself with different technologies can help you make better decisions about current or future products, as you often have a better understanding both of the different ways you can solve a problem and the tradeoffs involved. At PSPDFKit, experimenting is even part of our core values, a set of values that we agreed on collectively as a company.
As part of this experimentation, I switched technologies for a particular project and went from doing systems programming in C++ to some web development in TypeScript. In the following sections, I’ll describe what stood out about the experience, specifically in terms of programming languages, programming paradigms, frameworks, and tooling.
Programming Languages: TypeScript vs. C, C++, Go, Rust
TypeScript, which is a superset of JavaScript that adds static typing to the language, is becoming the “lingua franca” of modern web development — we even migrated our web codebase to TypeScript in 2021. Static typing helps detect errors before code is executed by checking that types are used correctly. Because TypeScript still needs to be converted into JavaScript, which is a weakly typed language, TypeScript’s type system is weaker than the ones in C++ or Rust. That means that some programming errors in TypeScript will cause an error at runtime, not at compile time.
One problem I had when learning TypeScript was the high amount of syntactic sugar in the language. Syntactic sugar helps reduce the verbosity of code, but sometimes at the expense of making it harder to understand, especially for someone without much experience with the language (or someone who isn’t up to date with the latest TypeScript/JavaScript features). For example, for a beginner, it isn’t trivial to identify that the following piece of code is an arrow function in JavaScript:
(a) => a + 100;
For someone not familiar with the language, the syntax above can easily be confused with an assignment. It’s also not easy to come up with words to describe the syntax to search for the underlying concept in the documentation. It’s true that C++ has complex grammar, and some parts of it are pretty daunting (ie. template programming), but if you restrict its usage to only a small part of the language, the syntax is more or less bearable. C and Go are extreme examples of having simplicity in the grammar of a programming language, so you’ll probably need some time to adapt to the complexity of TypeScript’s grammar.
Programming Paradigms and Frameworks (React.js, Redux…)
TypeScript is a multiparadigm language, which means it supports multiple programming paradigms (procedural, object-oriented, functional). Compared to a language like C++, the functional paradigm in typical TypeScript codebases is more pervasive. C++ also supports the functional paradigm, but it isn’t as popular. Possible reasons for that difference may be that the C++ syntax is less powerful and more verbose than the TypeScript one, or that C++ developers may be afraid that functional programming and functional data structures slow the program down. There are ongoing efforts to improve C++ functional programming capabilities in future C++ standards, but it’ll take some time before developers can use them in codebases. Modern systems programming languages like Go, and especially Rust, are designed to better support the functional paradigm, so if you come from one of those programming languages, the transition to TypeScript will be easier, as you’ll probably have more experience with designing your program with functional programming in mind.
Regarding the common frameworks for application development, systems programming and web development mainly differ in the number of them, and especially in the rate at which new ones are created. While systems programming typically depends on a few well-known libraries that are considered stable (STL, Boost, and recently, Abseil), the web development community isn’t afraid to use a high number of base libraries or frameworks (for example, React.js, Angular.js, and Redux.js.). New ones are introduced quickly, and codebases are keen to adopt them promptly, so you need to be prepared to constantly learn new frameworks. While this trend is common in software development in general, in web development, the cadence of learning new libraries is more frequent.
Build Systems and Tooling
To produce software from source code, specialized tools are necessary: a compiler/interpreter, a build system, and possibly some dependency management system in the form of a package manager.
Web development using TypeScript involves using a transpiler to JavaScript, tsc
, along with a build system with package manager support. The most popular package managers are yarn and npm. These are a welcome addition to someone used to C or C++, which are languages without a standard package manager. If you come from Rust, you’ll also feel at home with yarn or npm because the way their package managers work is similar.
Simplifying the addition of new dependencies can have its drawbacks, like suffering from an explosion of small dependencies. Dependencies are essential for code reuse, but it’s not uncommon in the web ecosystem that a project incorporates lots of dependencies for small pieces of functionality. One extreme example of a very small library is the is-odd library, which is a library whose only purpose is to return if a number is odd or not. Depending on lots of small libraries can cause problems in the long term, like having to deal with incompatible versions, unexpected security problems, etc. Sometimes it’s easier to implement the functionality yourself instead of depending on a small library. I recommend reading an article by Russ Cox, Our Software Dependency Problem, which talks about the importance of handling dependencies effectively.
Compile times in systems programming languages are usually slow, which contributes to a slow feedback loop overall. Web development offers a faster feedback loop, and thanks to this, you can iterate on the product you’re working on much more quickly.
IDE tooling is an important feature for the makers of programming languages for the web, so its quality is usually good. The latest language standards are readily supported, and the refactoring capabilities provided by off-the-shelf IDEs seem to be good enough. C++ is a particularly complex language to parse and index, so the refactoring capabilities have always been somewhat limited or unreliable.
On the negative or somewhat negative side, web development tooling changes very quickly, and it’s common for projects and libraries to require “bleeding-edge” tooling dependencies. This isn’t usually bad, but it can cause unexpected troubles. Systems programming environments are more conservative in general, and it’s normal for a project to keep using an old version of a compiler and dependencies without any external force, like a third-party dependency, forcing the upgrade.
Conclusion
Switching from systems programming to web development isn’t like using another programming language to solve a different set of problems. Both the nature of the problem that web development tries to solve (UI/UX in a web browser) and the legacy/tradition of the web development community influence the way development is done.
One of the main difficulties I had was learning some functional-based frameworks. Additionally, I struggled getting used to strong typing with dynamism and to bleeding-edge tools that favor an easy-to-use UI but whose output is by default more difficult to parse and work with from scripts, for example. Transition to web development will likely be smoother if you come from a systems programming language like Rust or Go, because those languages already import some of the concepts and workflows from web development languages like JavaScript/TypeScript.