5 ms·
> Can you write down the algorithm that you use to avoid writing memory bugs? Can you teach others how to do it? Structure the code in a way such that it is ob
by jstimpfle 8y ago
> Can you write down the algorithm that you use to avoid writing memory bugs? Can you teach others how to do it?
Structure the code in a way such that it is obvious what happens. Use "semantic compression" (e.g. be clear about your concepts and factor them in free standing functions), but don't overabstract/overengineer.
Eliminate special cases. If the code has few branches and data dependendencies, then successful manual testing gives already high confidence that it will be pretty robust in production.
Prefer global allocations (buffers with the same lifetime as the process), not local state. This also makes for much clearer code, since it avoid heavy plumbing / indirections.
I tend to think that modern programming language features mostly enable us to stay longer with bad structure. And when you hit the next road block, fixing that will be correspondingly harder.
- lmm 8y ago> Structure the code in a way such that it is obvious what happens. Use "semantic compression" (e.g. be clear about your concepts and factor them in free standing functions), but don't overabstract/overengineer. This sounds little different from "write good code, don't write bad code." I'm sure we all agree on these things, but I'm sure the people who write terrible code weren't trying to be unclear or trying to overengineer. > Eliminate special cases. If the code has few branches and data dependendencies, then successful manual testing gives already high confidence that it will be pretty robust in production. True enough, but that's so much easier in a language with sum types. > Prefer global allocations (buffers with the same lifetime as the process), not local state. This also makes for much clearer code, since it avoid heavy plumbing / indirections. That's a pretty controversial viewpoint, since it makes composition impossible (indeed taken to its logical extreme this would mean never writing a library, whereas the grandparent was convinced that more use of libraries was the way to write good code). > I tend to think that modern programming language features mostly enable us to stay longer with bad structure. And when you hit the next road block, fixing that will be correspondingly harder. Interesting; that's the opposite of my experience. I find modern language features mostly guide us down the path that most of us already agreed was good programming style, enforcing things that were previously only rules of thumb (and that we had to resist the temptation to bend when things got tricky). And so the modern language forces you to solve problems properly rather than hacking a workaround, and the further you scale the more that will help you.
- jstimpfle 8y ago>> Eliminate special cases. [...] > True enough, but that's so much easier in a language with sum types. These languages make it easier to have more special cases. There's a difference. > That's a pretty controversial viewpoint, since it makes composition impossible (indeed taken to its logical extreme this would mean never writing a library, whereas the grandparent was convinced that more use of libraries was the way to write good code). I don't see why that should be the case. Aside from the fact that composition/"reuse" is way overrated, libraries can always opt for process- or thread-wide global state. Another possibility would be to have global state per use (store pointer handles), and passing a pointer only to library API calls. The latter is also the most realistic case since most libraries take pointer handles. I absolutely have these handles stored in process global data. For example, Freetype handle, windowing handle, sound card handle, network socket handle, etc. Also called "singleton" in OOP circles. Singletons are nothing but global data with nondeterminstic initialization order and superfluous syntax crap on top. Other than that, they are indeed good choices (as is global data) since lifetime management and data plumbing is a no-brainer. > I find modern language features mostly guide us down the path that most of us already agreed was good programming style But just the paragraph before you said you didn't agree with mine? In my opinion, OOP, or more specifically, lots of isolated allocations connected by pointers/references, make for hard to follow code since there is so much hiding and indirection even within the same project/maintenance boundaries without benefit. In any case I absolutely agree that this style is not doable in C. You need automated, static or dynamic (runtime) ownership tracking.
- lmm 8y ago> I don't see why that should be the case. At the most basic level, if project A makes use of library B and library C, then you want to be able to verify the behaviour of library B and library C independently and then make use of your conclusions when analysing project A. But if library B and library C use global state then you can't have any confidence that that will work. E.g. if both library B and library C use some other library D that has some global construct, then they will likely interfere with each other. > Another possibility would be to have subproject-wide global state, and passing a pointer only to library API calls. The latter is also the most realistic case since most libraries take pointer handles. At that point you're not using global state in the library, which was the point. > you can always opt for process- or thread-wide global state That doesn't solve the problem at all. > Also called "singleton" in OOP circles. Singletons are nothing but global data with nondeterminstic initialization order and superfluous syntax crap on top. Indeed, and they're seen as bad practice for the same reason as global state in general.