It now dawns on me that types, tests and console.logs are really about being able to see the data that flows through our code.
Sure, types are necessary in some languages to help the compiler. But not in Typescript, where the types are useless when the program runs.
Same goes for tests: the tests only run when developing the system, not when it’s running in production.
As for the console logs, or prints, those also cannot be used in production (although often do stay behind, like tiny scalpels). Some structured logging can and should be used in production, but it’s purely for the benefit of further developing the system.
I am pretty sure that the main reason we use these three (plus a few more things) is simply to see the data that flows through our code. Code is like an imprecise negative image of the actual data that runs through our system. It deals with many possible data items (generally, an infinite number) that have to conform to a certain shape. But its flexibility is what makes it hard to see what data actually flows through it.
My second contention is that if you understand the data that goes through a piece of code (what comes in, how it changes and how it comes out), you understand the code. And conversely, that if you understand the code, it’s because you understand the data that goes through it. In other words, the only way to understand the code is to understand what data goes inside of it, how it is changed by the code, and how it comes out.
My third contention is that all of us programmers, even those with our firmly held beliefs about which tools are great and which tools are terrible, are just trying to be able to better see what data goes through our code, so that we can understand it well, and therefore change it with confidence. That’s it.
For those who prefer types, I now understand why: types give a good approximation to the data that goes through a piece of code. The fact that types are integrated into the IDE makes it even easier to get this benefit. The fact that the system will not let you move forward until you solve type errors (but tells you exactly where you went wrong) is also helpful, if sometimes annoying.
Me, I’m at the other end of the spectrum. To me, programming with strong types is like skiing with casts on. Sure, you can master it and it does prevent a lot of injuries. But I’d rather just get the cast after I ski. Now, how do I not fall off the mountain? By writing extensive tests that focus on the data going in and coming out of each part of the system. These data flows show (ideally) all the possible “families” of data that are processed by a function or endpoint, letting me understand it and change it. And a test that does not pass stops my entire test suite is also a helpful, if sometimes annoying, way to keep the system consistent. (Note: I also use strict runtime validations, which also help me see what data comes in – at its most platonic, the validations of the endpoint are the negative impression of the first half of the tests of the endpoint).
Now, it’s remarkable that people at the other end of the type spectrum are using types for the same reason that I use tests. And we all use console.log/prints (the exception being the elven folks that use debuggers; when I grow up, I want to be like you).
We’re all just trying to see the data in our code, that’s the whole game. If we can tackle this problem explicitly, and be a bit more flexible at how to go about it, we might be able to build systems that are based around this requirement. This is perhaps the main aspect of what I’m doing in cell, but I’m using this insight everywhere.