I find more modern languages so much less exhausting to use to write correct code.
I find more modern languages so much less exhausting to use to write correct code.
Modern languages do catch more programmer errors than C/C++, but the more general point is that there are "edge cases" (redirecting to a file isn't an edge case) that developers need to consider that aren't magically caught, and understanding the language you use well enough so as not to write those bugs is important.
The more experience I get as a dev the more I've come to understand that building the functionality required in a feature is actually a very small part of the job. The "happy path" where things go right is often trivial to code. The complexity and effort lies in making sure things don't break when the code is used in a way I didn't anticipate. Essentially experience means anticipating more ways things can go wrong. This article is a good example of that.
But GP’s point is that modern languages can surface those issues and edge cases, and try to behave somewhat sensibly, but even sometimes “magically” report the edge cases in question.
That’s one of the things which is very enjoyable (though sometimes frustrating) in Rust, the APIs were (mostly) designed such that you must acknowledge all possible errors somehow, either handling it or explicitly suppressing it.
let geese = usize::from_str_radix(line.strip_prefix("and also ").unwrap().strip_suffix(" geese.").unwrap(), 10).unwrap();
All these functions, usize::from_str_radix, str::strip_prefix, str::strip_suffix are Options which could be None, and we need to decide what to do with that option, Option::unwrap() says I promise this is Some(thing) and I want that thing. If you're wrong and the Option was None, Rust will panic.
Sure enough sometimes while rushing to solve an AoC problem you may get told your program panicked because you tried to unwrap None. Oops, I wrote "geese" above and thus didn't handle the scenario where it might say "goose" in the input when there was only one of them... need to handle that properly.
In a C program taking the same approach it's easy to skip that unwrap() step. After all we're sure it's fine... aren't we? And in the absence of any error reporting now all you know is AoC says the answer you got was wrong and you've got no idea what the problem is.
>In a C program taking the same approach (...)
"Writing C" and "rushing" is a strategy that cannot give you correct, robust software.
Just taking some simple release dates [1] or wikipedia I found:
Ages of "Yes" group: 49, 36, 22, 26, 26, 12, 31
Ages of "No" group: 11, 14, 33, 7, 32, 45, 26, 34, 21
Averages: Yes 28.85, No 24.78
With the ambiguity around what "age" even means for the language here (e.g., counting the age of node.js or python) it is probably meaningless, but it seems well mixed independent of age.
The hidden costs are enormous and to this day still not very well accounted for.
There's no garbage collection/reference counting/etc. going on in the background. Objects aren't going to be moved around unless you explicitly move them around (Enjoy your heap fragmentation!). In C, you don't even get exceptions.
Of course, this creates TONS of foot-guns. Buffer overflows, unchecked errors, memory leaks, etc. A modern language won't have these, except for memory leaks, but they're much less likely to happen in trivial to moderate complexity apps.
A modern language could automatically throw an exception if the string cannot be completely written to standard output.
But that has not necessarily helped. The program now has a surprising hidden behavior; it has a way of terminating with a failed status that is not immediately obvious.
If it is used in a script, that could bite someone.
In Unix, there is such an exception mechanism for disconnected pipes: the SIGPIPE error. That can be a nuisance and gets disabled in some programs.