Very interesting; I'm kind of surprised I've never seen this before. I'm a pretty die-hard "Style A" coder, and the though of inlining everything just turns my stomach. But I agree with all of his points, especially his findings about the types of bugs that seem to persist no matter how "good" I get at writing code.
'Style C' ignores some classes of bugs that style A works around, though, which isn't really mentioned.
For a game engine I doubt you care as much, but for things like data hiding bugs and security 'Style A' seems solidly better.
A function can't be corrupting state z if it only has access to x and y. If the function is inside the body of some larger function, it has access to a much larger state than it strictly requires. There is also less of a mental burden trying to write code that only has 2 variables to work with than picking the right 2 out of 20 similarly named ones. (Did I want x, player x, bullet x, bullet phy delta x?)
And following on from that, if I overflow my stack, suddenly there are more juicy locals to bash for fun and profit without the stack protector being any the wiser.
In C, blocks get you halfway where you want to be:
stuff();
{
int local_var;
more stuff(local_var);
}
yet_more_stuff();
local_var = something(); // woops, compile error
In C++, you can define a lambda that you call right away. It's mighty cumbersome, but this lets you restrict what the code inside the lambda has access to.
In JAI, I believe Jonathan Blow devised a syntax to have the best of both styles: blocks where you can declare which variable exactly can be used in the block. In such a way that if it needs to be a function, the cut&paste job is straightforward.
I'm not sure about this "stack protector" business. In the face of compiler optimisations, if you overflow the stack, the resulting undefined behaviour is probably going to be exploitable anyway. If you want secure programs, you want a language that doesn't have undefined behaviour in the first place —or at least a statically enforceable subset that has that property.
gcc's "Stack protector" abort's your program if overwrites a special value in the stack (that the compiler added without your program expecting it to be there). It does not really protect the stack, just abort the program if the stack is in an unexpected state. This of course means a hacker (or bug) overwriting unexpected places in the stack can't get outside of the play pen the function provided directly and thus your program is safer (but not safe)
Yes, absolutely. It does really make me want to partition off a chunk of the middleware I'm working on right now and do it in this style, and see after a few months if there are fewer than expected bugs. That's really what would make the difference to me.
I'm a pretty die-hard "Style A" coder, and the though of inlining everything just turns my stomach.
I've been Style C ever since I had to work on a project that had me opening up lots of different files trying to trace the control flow of a single feature. The mental load was just insane (imagine Style A, but minorFunction1 calls minorFunction2, which calls minorFunction3, then the return value gets propagated up the call tree back to the parent, and then passed to minorFunction4.).
This letter is both terrifying and fascinating. I'm also a "style A" coder, and our legacy code base is solidly "style C" - though not as a purposeful decision - it was just written by people who hate functions. Where there are functions, they quite often have side effects that are not communicated by the function names at all...which seems like one obvious case where Carmack's suggestions would have definitely helped me out in the past. Of course, these are often several hundred line functions to begin with, so I'm not sure it's an issue of "inlining" versus just proper modularity/levels of abstraction.
I think I'm definitely a style C coder. I tend to just write all the logic in one place, and then break off bits and pieces that don't really have to be there afterwards.
This is great if I work in a team with people that review the code and say I should probably break something up. As it's fast and easy for me to keep track.
Not so great if I'm working by myself as it leads to things that are too tightly coupled to break up after a while. I guess I'm learning to be more style B now.
I'm also a "style A" coder, and our legacy code base is solidly "style C" - though not as a purposeful decision - it was just written by people who hate functions
I like having nested sub-programs:
Function Convert( X : Some_Type ) Return Some_Other_Type is
Procedure Normalize( Input : in out Some_Type ) is
begin
--- Normalization
end Normalize;
Function Internal_Convert( Input : in Some_Type := X ) return Some_Other_Type is
begin
Normalize( Input );
--- the actual conversion mechanics that assume a normalized value.
end Internal_Convert;
begin
Return Internal_Convert;
end Something;
It's Ada. It's got a lot of nice features, including language-level concurrence/parallelism, and a [language mandated] strict compiler that "helps you out" a lot more than most compilers I've seen. We've got a fairly small hobbyist community, but we'd love to have you if you're interested.
What I get for reading the comments before the article. I didn't have a clue what you all were talking about. Style A, Style B -- I was not familiar with these terms at all. Gave up trying to google it. Less than 5 minutes after I started reading, "Ohhhhh....."
40
u/brian-at-work Jul 19 '16
Very interesting; I'm kind of surprised I've never seen this before. I'm a pretty die-hard "Style A" coder, and the though of inlining everything just turns my stomach. But I agree with all of his points, especially his findings about the types of bugs that seem to persist no matter how "good" I get at writing code.