Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Well that's precisely the mindset of C/C++. You have to think by yourself about everything that can go wrong with your code. And, man, lots of things can go wrong.

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.


> 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.

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.


Indeed. One of the things you notice when writing say, Advent of Code solutions in Rust is that you're writing unwrap() a lot e.g. something like

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.


> Sure enough sometimes while rushing to solve an AoC problem (...)

>In a C program taking the same approach (...)

"Writing C" and "rushing" is a strategy that cannot give you correct, robust software.


Exactly. Whereas writing Rust and rushing can give you correct, robust software.


Exactly. And isn't UNIX all about writing tools that can be used in unintended ways and combinations?


Considering that awk and TCL do not have the bug in question, while java, ruby, and node.js do, I'm not sure this can be framed in terms of modernity


I just realized I never thought about why System.out.println doesn't declare an IOException. Turns out PrintStream silently catches the exception and turns it into an error flag no one ever checks. Undermining both the Ability to handle IO errors using exceptions and making it impossible to find out what happened over what I assume was the ability to call System.out.println without checking for errors. Right now I am just happy that my IO code generally writes to binary streams so I don't have to rush through my code base to check for that nasty surprise.


Well, I'd say that the older languages mostly get it right, while the newer languages mostly fail.


I am not sure either follows. But it depends how we even define "older languages", especially considering differences between python 3 and 2, are they the same age (based on the original python release) or are they treated for their respective release version?

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.

[1] https://blog.sunfishcode.online/bugs-in-hello-world/


Aparently I copy-pasted the wrong link:

[1] https://iq.opengenus.org/age-of-programming-languages/


Yes. First you learn to 'code stuff' and as your experience progresses in that language you merely learn more and more of the ways it can be wrong, and then you worry more, and have to overthink every little thing.

The hidden costs are enormous and to this day still not very well accounted for.


C/C++ basically do only exactly what you tell them and nothing more, which is why they're so much faster than other languages.

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.


In this case, I don't see how they help.

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.


In C, yes. But for C++ you can use exceptions if you want. You just have to "protect" all the "primitives" and after that you're safe.


And the mindset can be to treat such conditions as "Don't Care" to allow the C/C++ code to generate more optimized assembly.


You could have a way to explicitly flag that, instead C will assume that any accidentally silently discarded result can be optimized around with and kick of a landslide of changes that turns bugs ten times worse.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: