The author's first point about establishing conventions so that you can re-use the code that works with those conventions is very important. At Google, nobody writes code to serialize/deserialize bytes, because the default answer is just "use protobufs". Nobody writes low-level communication protocols (well, outside of some very-specialized infrastructure teams), because there's one RPC system. There's one standardized naming system, and a mostly-standard logs format and method for analyzing logs. If you do batch computation, there're two solutions, and there're only a handful of different file formats and storage engines, certainly less than in the open-source world.
I think Rails and Django (and Lisp) discovered the same principle: if you get everybody writing their code & data files the same way, you can write tools to manipulate those files, and that saves you way more in productivity than trying to get the perfect file format.
Autotest: pickles over ssh stdin/stdout. They would have also had base64-encoded pickles in mdb labels if I hadn't stopped them. Or who knows, maybe they went through with it with me out of the way.
Logjammer/Treehorn: XMLRPC. (Yes, what a horrible project name. Juveniles.)
There are others. NIH actually happens inside the company too.
I really do not understand why they chose to do so. In my opinion, it does not improve readability at all, even worse, when the spacing between type and name gets big, your eyes need more work to figure out the correct line relations.
Furthermore, this can generate horrible commits, for example:
int x;
int y;
becomes:
int x;
int y;
float z;
after adding ONE single variable. But your commit will contain changes for the above 2 lines. This will make for an insane experience once your variable list grows to a certain size (including multiple spacing changes) and you intend to rollback/cherry-pick/time-warp. Then you will get to deal with the full wrath of conflict resolution. Also, your commit (of one logical line change) contains way to much meta info, which also decreases commit/diff readability.
If trivial whitespace difference causes wrath fill conflicts then I think you have a deeper issue. I can't think of a situation where this would be even a minor issue.
Not a huge deal, but it hides the actual author of that line when you're doing a blame. I try to only change the precise lines I need to in a commit, and all of them are relevant to the commit message. That way it's usually a very quick check to see what commit added a certain line.
If I absolutely need to do some tidying in a file, I do it in a fully separate commit so that the change can not be construed to be related to the other feature.
hg annotate --help
...
-w --ignore-all-space ignore white space when comparing lines
-b --ignore-space-change ignore changes in the amount of white space
-B --ignore-blank-lines ignore changes whose lines are all blank
If multiple people rearrange the white space at the same time as adding variables (e.g., because the new variables are of types whose names are different widths, both wider than the old gap), you are more likely to get a conflict than if they just leave the spacing alone. Many version control systems seem to tend to do their automated merges line by line, and this sort of edit seems to give them less to work with. [8c99a51796e06219f472f78a5c081dd664da83dc]
Then the wrath comes when you get latest (pull, update, fetch, rebase, call it what you will) and have an inconvenient merge forced on you! I've always personally found the fact that it's just adding or removing white space especially galling :) - but tastes may differ.
I do use vertical alignment, but that's not necessarily how I'd align the above lines. Assuming y and z are related, but x is not, I'd do them like this:
int x
int y
float z
I might add a blank line between x and y, too.
If you do vertical alignment such that it emphasises semantic relations, you avoid commits that change more than they should, and it also helps draw your eye to relationships between variables.
I'll make exceptions myself if there is one declaration that is just way longer...
int x
int y
float z
MyReallyLongTypeHere foo;
Other than that, I tend to prefer having the variable names line up. Though doing mostly JS and C#, I can use var pretty much anywhere an assignment happens, even with null initialization.
var foo = (sometype)null;
The bigger issue to me is comma first vs comma last... I find that comma first is easier to notice a missing/extra.
You usually aren't looking for a line based on the type... you're usually looking for a specific variable... with your variables lined up, that portion becomes faster... scanning for y in your second example without the variable declarations lined up for example. This advantage outweighs the slight hinderance of the separation on a given line. That, and many editors will highlight the line with the cursor on it. Which alleviates the issue you point out a bit.
I do that, and I don't find it to be that much of an issue, myself. In my experience, it's rare that I add a new variable to a funtion, so I guess your mileage may vary.
I'm guessing that Carmack did a lot of assembly language programming and that the vertical alignment comes from that. I've done a ton of assembly programming and I find myself aligning code in C because I find it easier to scan.
It's funny you mention that, as I'm using the companion program (http://google-styleguide.googlecode.com/svn/trunk/cpplint/cp...) on the code for my current project right now. My only complaints is that much of the cpplint program checks for arbitrary style choice versus possible issues. Sure, most of the style I agree with, but I've actually hacked on that program a bit because the style guidelines are hardcoded in it. I'm halfway tempted to make it more flexible in terms of style, or at least separate the style components from the linting parts.
That's my pet peeve about many linters as well - I don't use jslint because it has many of Crockford's personal style tastes embedded in it, several of which I disagree with. In cpplint's (and perhaps jslint's) defense, it was written for one specific organization with broadly-accepted style guidelines, and open-sourced "in the hopes that it'll be useful, but without any warranty of any kind".
jshint was started in part to separate out the actual warnings from Crockford's style in jslint (it is also supposed to be more configurable with regards to style-type comments). It might be worth a look if that is the only reason you disliked jslint.
> C++ code can quickly get unruly and ugly without diligence on the part of the programmers. To see how bad things can get, check out the STL source code. Microsoft's and GCC's[5] STL implementations are probably the ugliest source code I've ever seen. Even when programmers take extreme care to make their template code as readable as possible it's still a complete mess. Take a look at Andrei Alexandrescu's Loki library, or the boost libraries—these are written by some of the best C++ programmers in the world and great care was taken to make them as beautiful as possible, and they're still ugly and basically unreadable."
Finally, a guru dares to call a spade a spade. Header-only, template-only C++ programming is a mistake! After 1995 C++ Standardization took the wrong path and lost contact with real world developers. The glorification and idolization of 'STL programming' was in sharp contrast with programmer's needs. Today in C++ there is a chasm like in no other language between the 'official language' and what programmers need and use day in day out.
What's odd is that the C++ community fetishizes these techniques so much. I mean, when faced with problems that require running code at compile team, the lisp community's answer was "ok, just include an interpreter and run your lisp code at compile time with eval-when". The C++ community first insisted that there was never a reason for doing so and then said "ok, but instead of writing code for compile-time execution using C++, we're going to create an nightmarishly complex and crippled language that you'll use instead; it will be so painful that you'll be proud of implementing factorial".
And now a whole new generation of programmers can gaze at the horror of Modern C++ metaprogramming and conclude that metaprogramming is bad....
As far as I know, C++ templates were never intended be turing complete. They turned out that way by accident.
If you'd asked the standards body to add a turing complete type-level meta-programming language to C++ in order to generate code at compile time I suspect they'd have told you to get knotted.
What people actually asked for was a reasonable syntax for adding generic functions to C++ that would not carry any runtime cost. Sounds completely reasonable, right? The standards group said "sure, how about this?", and kept adding more perfectly reasonable individual requests to the syntax like template specialisation. Only afterwards did the true nature of crawling horror that they'd inadvertently unleashed become apparent.
Any style of programming can look like a mistake if you take it to crazy extremes. I find header/template C++ programming works very well provided you do it in moderation and keep things simple.
Remember the STL is complicated because it tries to be super generic, and it tries to be super generic because it's a library so it tries to cater for all possible uses. If you're writing a program instead of a library, you can make things orders of magnitude simpler because you only have to provide what you need, not everything any programmer on Earth might need.
Java Swing using anonymous classes to 10 or more depths to represent callbacks because OOP and inheritance > all.
Haskell having one file IO operation a thousand feet below the surface of a program "un-purifying" the entire call stack with side effects because pure functional is king.
Trying to implement any generic anything in C, because in procedural having template or inheritance based polymorphic behavior is crazy, so you end up with 5 million ways to write readNumber() for every numeric type ever.
So go figure, procedural, meta, functional and OOP all go completely deep end when you try to kitchen sink them as the be all end all solution to all problems and woes.
> Haskell having one file IO operation a thousand feet below the surface of a program "un-purifying" the entire call stack with side effects because pure functional is king.
And that's the way it should be. (Refator your programme, if you want to keep the other functions pure. But it is a Good Thing (TM), that you can not hide your IO if you pile on enough layers.) Haskell has other problems, though.
Bear in mind that the person making the comments you quoted is not actually John Carmack, who I will agree is a "guru". Carmack's own comments later indicate that the style represented in Doom 3 is an intermediate point in his personal evolution. Indeed, this style was a common one 10+ years ago and may have been appropriate for the less reliable compilers and buggier STL implementations of the day. It's certainly still popular with many programmers, though, as you imply in your last sentence...
Considering how many languages these days completely eschew the header/body paradigm, I'm not sure that "head-only" programming is such an obvious mistake.
Such languages tend to be reasonably smart about what changes trigger large recompiles, and about not re-parsing the same lines of header-only code over for every compilation unit. In C++ the best you can hope is to set up precompiled headers and live with the artificial dependencies.
If the only problem you have with header-only programming is compile times, might I humbly suggest that the problem you are actually concerned about is entirely somewhere else (hint: most language compilers are terribly unsmart about what changes trigger recompiles, as the cost of compiling the entire source tree isn't too terrible).
I've loved John's code since I saw it first time when the original Quake was leaked from their FTP site through IP spoofing. I was just a kid at that time, and it was an amazing experience to hack it.
Yet now, the first example that I saw in this article hurts my eyes. Compare:
Well, let me state this: John's version is not to my preference 'cause it has { in the same line as if, but I can live with that. But YOUR version uses not only the ? operator, which should be burned with fire but it NESTS two of them together. Please, I want to die now. :(
nested ternaries are the same as if-else-if continuations. You figure that out one time, and it's easy to parse forever after.
The ternary operator in general eases understanding because it extracts the common bit, i.e. "sides[i] =", meaning you don't have to carefully read the contents of each and every conditional block to verify nothing else is going on besides this one assignment.
The chained ?: idiom is very common in some C codebases. Having experienced it there, I find it highly readable, but it can sometimes benefit from parentheses to clarify precedence.
How would you initialize a variable that depends on the truthfulness of another without the ternary operator. You should be careful when using extremes in programming.
I agree that it's visually displeasing, but it's very easy to actually read and understand.
The only reason I was able to quickly understand your beautifully-aligned code was because I had read the logic in the previous block, which is stupidly-easy to parse.
I also (like many others) shun the use of nested ternary operators. Probably because I'm used to dealing with PHP's straight-up broken implementation.
Further, I'd have split the side being determined from the sides array, as it's rather mentally taxing to figure out the increment at the end:
if (dot < -LIGHT_CLIP_EPSILON) {
side = SIDE_BACK;
} else if ...
sides[i] = side;
counts[side]++;
Perhaps it's because I come from a functional programming background, but I find your re-written version using the ternary operator is much easier to understand, and prettier to boot. Well done!
This is a great example of "factoring out" that satisfies both the computer science and the mathematical definitions of the term. The assignment "sides[i] = " is repeated inside the if block, and it can be "factored out" using the ternary operator, just like in math:
(a * x + a * y + a * z) = a * (x + y + z)
I'm even tempted to say that "assignment distributes over if", but that might be pushing the analogy too far. Any further and I might start thinking about monads, so I'll stop here.
This example turns the code into something that appears more airy but in fact is much harder to understand due to extensive use of ? :.
I find that one of my own major steps toward programming maturity happened when I stopped doing goofy things like this and started writing code that was as simple as possible to logically follow, and that was as un-special-cased as possible. (By this latter I mean, if you change the code a little bit, you don't have to rewrite it; it looks basically the same. Imagine you want to do more than just assign one variable inside the clauses of the 'if'. In the Carmack version you just add more code there. In the proposed substitute, you have to rewrite the whole thing.)
...really? I don't even write C that often, and I didn't find that difficult at all. Even if you don't grok the syntax yet, it just does what it seems like it does at first glance. And how on earth does a two-long ternary chain strike you as "extensive"?
If you want to do something in addition to assignment, it's really quite easy: Just add a new `if` block underneath. This clearly separates your conditional assignment from whatever else is going on that may or may not be related to the assignment. (What if you add something besides assignment to the if, but then want to change the conditions for only that? You have to rewrite the whole thing!)
I agree with jblow. I consider ternary a "sharp knife" in that even though it saves space, I never want to see it used more than one level deep. I don't want sharp knives in my code, I want Lego bricks. If that means the source code is a little bigger or the runtime is a little slower, that's OK, the vast majority of the time.
The most egregious example of I can think of in this kind of thing is JS "gurus" who try to use the fewest semicolons possible in their unminified source. It doesn't add readability, and the semicolon rules are complicated enough that one could easily write a bug while trying to stick to the style.
Sometimes languages have "tricks" that aren't sharp, and those are OK to use when you know them, but one has to distinguish.
I like the last one better. I think it reads more like "assign one of these values to sides[i]" and less like "do one of these three things".
"By this latter I mean, if you change the code a little bit, you don't have to rewrite it; it looks basically the same."
I'd say that's kind of the point though. The first one would look "basically the same" if in the last else it assigned to sides[j] instead of sides[i]. In the last one it would look very different if it was about "doing stuff" rather than choosing which value should go to sides[i].
My point is that when you are writing complicated production code, and you are a good programmer such that your rate of features successfully implemented is high, then you will often be going to old code and changing that code to behave somewhat differently than it was before.
When you do this, you want that old code to be like putty. You want to bend it into a new shape without having to break it and start over. Sometimes it really is better to break it, if bending would be too messy or cause problems later or otherwise sets off a red flag in your head. But if you have to break and re-make everything all the time, you won't be a very fast programmer. So you learn how to bend things, elegantly.
And after a while of this, you learn how to write code that is more amenable to elegant bending in the first place. When you type code, you are not just implementing a specific piece of functionality; you are implementing that functionality plus provision for unknown future times when you will need to come back and make the code different.
(To link this more thoroughly to the previous comment: it happens all the time that you write code that is not really about doing stuff, but then you later need to make that code be about doing stuff. Sometimes this is for shipping functionality reasons, sometimes it is just to temporarily insert hooks for debugging. Declaring in advance that this code shall never be about doing stuff is usually a mistake.)
It strikes me that your argument taken to the extreme is that everybody should program in assembly language because you can do anything, anytime, anywhere. Well, at least as far as control flow structures are concerned. Certainly C is preferable to C++ if you want simple and malleable code.
Do you also prefer if-else to switch statements? (I'm not sure.)
Do you like to use goto? (I doubt it.)
Do you eschew the use of classes and inheritance? (I doubt it.)
Do you keep all your code in one file? (I'd be very surprised.)
My point with these semi-facetious questions is that structure is important and regularity in a codebase does wonders for comprehensibility and maintainability. I agree with you for the case in which you do actually have something where the underlying structure is likely to change, in that then it does make sense to write it with malleability in mind.
Consider something like command line options processing. You have 50 options. You might want a 51st option. It makes sense to use the most regular structures you can so that people don't start special-casing stuff in the middle of it all. That, or to use an options library that defines the allowable formats for you.
Part of me thinks you're just having an allergic reaction to ?: simply because it is pretty unusual the first time you see it used seriously. But it can simplify so much:
No, you clearly didn't understand my point. I am talking about maximizing the rate of successful features implemented. Programming things in assembly language is obviously not going to do that.
I don't know, man. I have 31 years of programming experience. I am not detecting from your argument that you have anywhere near this level of experience, so I am inclined not to get into this discussion. But I will say that your code example at the end of your comment is exactly what I am talking about. It happens all the time that I want to put something in front of 'return b' (or, in fact, I just want to put a breakpoint on that line in the debugger! Not going to happen in your second example...)
I'm not even clear that it's clear I didn't understand your point. I guess I have trouble communicating effectively. I thought your point was that flexible control flow structures like if-else allow you to change code quickly and more easily in the future. Wasn't that the point?
Assuming I have understood your preference for flexible code, I am simply stating my belief that it is useful to balance this with rigid code. Sometimes the flexible code is simpler, easier to read and write, and more maintainable, sometimes the rigid code is simpler, easier to read and write, and more maintainable. It depends on what you are trying to do. In this particular case I prefer the ?:.
The assembly argument was based on a notion I had that any time you use something more complicated than test and branch for control flow you are introducing rigidity into your code. Often this lets you be productive too; switch statements are a good example.
And lastly, let's assume I am 14 years old. Is that really the way a wise teacher talks to the young and inexperienced? My favorite teachers ask questions to check and help deepen my understanding, and sometimes it's revealed that they're learning something too.
Which you can't know in advance, which is the point. Since you don't know what you might want to do with stuff down the road, you end up writing code in a style that is less concise but more "putty-like". I am an amateur programmer with derpy years of experience, but I found myself often taking "shortcuts" out after a while, because they made working with the code harder.. just like the poster said, I realize that trying to be super clever all the time isn't that helpful. I guess you could say in places that are subject to constant change or lots of copy and paste, I trade screen space for putty-ness, and when something turns out to not really change anymore, or generally gets in they way of more interesting things, I compact it a bit.
Sometimes rigid code is simpler, sure. But what I am arguing is that it is almost never more debuggable / maintainable.
What I am saying is not specific to test and branch, though test and branch is great because it gives you these big code blocks into which you can insert more code and it's clear where that code lives and under what circumstances it runs. Which is something you don't get in assembly language, which is part of why the assembly language reply is a goofy straw-man argument.
Yes, my reply was a bit irritable; I would definitely prefer to have a reasonable discussion, but the assembly-language thing was the first volley in being unreasonable. Putting up a straw man like that is an attempt to win the argument, not an attempt to understand the other person's position. I detected this and decided, well, if that's the position, then it is useless trying to make further / deeper rational arguments, so I am just going to say, this comes from a lot of experience, so take it or leave it.
As fatbird replied, "This is shitty." (I can't reply to his reply yet because of the timed reply thing, so I am including it here.) Maybe it is shitty, I don't know, but it's true and sometimes you just have to say the true thing to be expedient and get on with life.
I don't have time to teach people on the internet how to program. I work my ass off for 4 years at a time to build and ship games that are critically acclaimed and played by millions of people. These are the kinds of things most programmers wish they had the opportunity to work on, and wish that they knew how to build. (Often programmers think they know how to build these things, and then they go try, and they fail. It is a lot harder than one thinks). I am not saying this to brag, because I honestly don't feel braggy about it right now. It's just fact. I am pretty good at programming (probably not as good as Carmack) and I have worked really hard for a long time to be as good as I am. Meanwhile I am also trying to be pretty good at game design, and oh yeah, running a software company.
So when I give advice like this, and someone retorts, and it seems to be coming from a place of lesser experience, it is not really worth my time to get into a serious argument. I am not going to learn anything. I have been in the place where I had that kind of opinion, many years ago, and then I learned more. Fine. I can either be polite and quiet about it, or say something a little bit blunt and rude, in the hope that the other person (and maybe any bystanders to the conversation) will seriously re-consider what was said in light of the new information that it comes from someone who is maybe not a joker. I can't spend a lot more time than that teaching everyone in the internet how to program, because it takes almost all the energy I can muster just to build software. (Though occasionally I do write up stuff about how to program, and give lectures bearing on that subject, like this one: http://the-witness.net/news/2011/06/how-to-program-independe...).
Of course this don't-get-into-the-argument strategy of mine has at least partially failed, since here I am typing out this really long reply. I don't know.
I used the word extreme in the assembly language argument to indicate
that I understood you weren't actually advocating people program in
assembly. So I don't consider it a straw man. Rather, it was an
example of extremely flexible control flow. Sometimes this is what
you need. Sometimes you need to modify the stack so that a function
is called in a different way. Sometimes gotos can provide
significantly more efficient code. Sometimes vtables are too
expensive. Rails is at the opposite end of the spectrum, it is very
rigid. As a refutation to your argument against rigid code, a lot of
people consider Rails code to be debuggable and maintainable.
Personally, I like a middle ground and try to be aware of the costs
and benefits of making and using cookie cutters, an example of which
is chained ?:.
I was not asking questions to "destroy your argument", I was trying to
establish different contributors to rigidity / flexibility. It seemed
to me that in your argument you were dismissing rigidity - certainly
you weren't praising it - and I wanted to point out that in the use of
any control flow more complicated than test and branch you are in fact
relying on rigidity. If this is a "straw man" in your eyes, then so
be it. Having functions introduces rigidity! Even if-else enforces
some things. If anything, I was just actually interested in the
topic, because I hadn't really thought much about the tradeoffs of
rigid vs. flexible in those specific terms and I wanted to explore
them a bit.
What if I revealed myself to you, and then you were like, oh shit, I
better take what he says a little bit more seriously, wouldn't that
just be embarrassing? I don't want to do that to you.
"Plus, I mean, what if I revealed myself to you, and then you were like, oh shit, I better take what he says a little bit more seriously, wouldn't that just be embarrassing? I don't want to do that to you."
No, by all means, go ahead. I am interested in having a productive discussion about programming, so if you can share your experience in a way that convinces me, I am totally open to it. If it turns out I am wrong, I won't be embarrassed, I will just change my opinion so that I am not wrong any more. This is how one becomes a good programmer in the first place: by paying attention to what is empirically true, rather than what one is originally taught or what seems exciting or what is in theory better.
I cut out the creepy parts, I agree that they were. I can't reveal my experience without breaking anonymity. If you need to know about my experience for my examples and arguments to hold water, then my points aren't very good anyway, which is why I prefer to stay anonymous. Good luck with The Witness and I'm sorry that this was painful. Symbolism and meaning in games are important and I appreciate what you are doing.
I have 31 years of programming experience. I am not detecting from your argument that you have anywhere near this level of experience, so I am inclined not to get into this discussion.
int a_val = a;
if (a_val)
printf ("returning b");
return a_val ? b : c;
Anyway, it's not useful for complicated code that needs to do lots of stuff. It's useful for simple code that ends up being more verbose with if-else. It's also useful for enforcing behaviour.
For quick test programs where I might want to change some number of iterations without having to recompile, but I also don't want to have to provide an argument every time I run it.
You're assuming that the first argument here is an integer value. For a single, one off, testing haress application I wouldn't worry too much about applying a full-blown semantic code style.
Is there anything wrong with keeping the default value as a separate constant?
const int kDefaultValue = 1000000;
int value = kDefaultValue;
if (argc > 1) {
value = std::atoi(argv[1]);
}
const int count = value;
Personally I much prefer using [program options][1] from the boost project (since we are talking about C++ here). Most projects already have this as a dependency, and its quite easy to setup. It also properly handles your types.
Perhaps I should have clarified: when I said quick test program, I was referring to something I would write to convince myself of some minor detail (sometimes just checking compiler warts or algorithm performance) and that piece of code would never be seen or run by anyone but me. I know that the first argument will always be an integer, because I'm the only one who will pass it arguments! I'm also (kind of unreasonably) a const nazi. So given the option, where I can take a shortcut (I know, bad, bad programmer), but still do "proper" programming (consting everything by default is my SOP), I'll do it. And I'll never release that code (I'm starting to regret posting that snippet here . . .).
I will agree, though, option parsing libraries are a definite must for released software. I like Boost, but using even small parts of it tends to pull everything in, and at least for my current project, we are trying to minimize dependencies (it's a library). I had to fight for Boost::regex, and only got it as a fallback for compiler versions that don't have regex.
I wasn't lambasting you, and I wouldn't ever regret posting a snippet. It helps drive the conversation.
I understand and agree with Boost injecting a whole lot of dependencies, and for a small testing library or executable I would probably got the same route that you did. I'm also a pedantic const nazi at times, and merely replied back as it makes me feel that, in some else's code, they may find this useful.
If you take a look at the Doom3 code there are even places where they stray from the code style guide when it makes sense. I'm more of a proponent of "in the moment" styling to make sure that it matches the rest of the project, or at the very least, component that I am working on.
Well, I might be unusual, but I don't object to goto, I don't uses classes and inheritance much - and I like code that's all in one file, too. (Means I can keep an eye on it.) So it might not surprise you to hear that I'd vote for the code that uses the if statement :) - in fact, I don't really understand why the second would ever be preferable, outside some unlikely case specially engineered to prove me wrong.
I can pretty much promise you that at some point, you will find yourself wanting to debug one case, but not the other. If not this specific code, then some other code very much like it. If not you, then somebody else! But if the code is all on one line, how will you stop on one case and not the other? In every native debugger I've ever used, you can't. You need to split it into separate statements, so you can breakpoint each case separately. So in the long run, the code is very likely to be changed, so it'll probably end up the first way eventually. So why not just write it that way to start with?
("Ah, ah, but but, but have you heard of this thing called a conditional breakpoint?" - yes, I have, thank you.)
It's just not even funny to think about how much of my time this specific issue has wasted over the years :(
All you need to do is hoist the conditionals out of the ?: if you have a bug and you want to inspect them, either with printf or a debugger. Everything else is available before the statement.
Personally I don't use debuggers except to get a backtrace from a core dump once in a while. I use various forms of automated testing and logging to stderr/stdout. They just aren't very useful with concurrency bugs.
The "all you need to do" bit is exactly the way this has wasted my time. I'm not saying that doing it is particularly complicated, nor that any individual occurrence is especially onerous, and in fact it usually isn't (though you do lose your state, and that's annoying) - but the time taken mounts up!
Ah well. I've said my piece. If your experience hasn't convinced you, like mine convinced me, then I imagine yours was a lot more fun ;)
Note that my response was mainly to the "hard to understand because questionmarks" part of your post. I think that's a pretty weak argument, and that being clear about which part of the code depends on the ifs easily makes up for weird syntax or whatever. The "that is goofy, this is mature" stuff is ridicilous.
I get that there are other reasons for sometimes choosing if-statements instead of if-expressions in cases like this. But then it quickly comes down to a bunch of technicalities (language can't do this, debugger can't do that, ...), and really, even if we're talking C++ stuff only I would not agree with it as general advice.
Precisely. And there is even a little bit more to it.
1. it reads "assign one of these values to sides[i]".
2. it would not allow some other peoples spurious code into the assignment.
which is a good thing.
3. space is used to convey meaning; note how sides[i] stands next to dists[i]; ternary operation is formatted as a table, etc.
If you have people adding "spurious" stuff to your code, you have a bigger problem than coding style. Why would you need locks on your door if you live on a planet with only friends on it? If you don't; why don't you?
But in this particular case, some trivial extra code inserted inside that if statement may break things. Like breaking vectorization of that 'for' (note, that we are going over vertices).
When you are writing the code, some times you may want to put additional constraints on the allowed operations, modifications, etc. A trivial example in C++ would be using 'const &' instead of '&'.
This is true (especially: breaking vectorization). But premature optimization is not a good idea, and if you are doing real optimization, you are going to rewrite that piece of code 10 times anyway, so it is in a different class of problem and the putty stuff I was saying before does not apply (i.e. this code is in the 1% or so of the codebase that is highly performance-sensitive).
Optimized code is just a different thing from general code (if one is a productive programmer).
And again it depends. I would return to a simple example of passing a parameter by a const reference.
When you are writing this const in the "const std::string &name" you are 1) constraining developers from breaking things 2) making the code more efficient 3) providing clues to other developers 4) providing clues to optimizer.
All of these points are important to some degree. And it is just the same, when you are writing this assignment.
And writing efficient code that provides all these clues in every way possible (including consistently and meaningfully arranging the white spaces) is certainly a good idea. Funny thing, that with experience it doesn't take extra time to do that. You just write it, and it comes out in the right way: readable, efficient and optimal down to CPU microcode and aligned memory accesses.
I think we have different ideas about what constitutes optimization.
Sure, putting const in parameter declarations is easy to do. It may even buy you a little bit of speed because the compiler is a little bit clearer about pointer aliasing and whatever. But it's not going to make a difference in the equivalence classes of slow code / fast code / Really Fast Code.
Serious hardcore optimization usually involves changing the way the problem is solved to something different than the way the old code thought about it: either constraining the problem space further, or attacking it from a different direction. This usually involves rewriting everything since there are so many cross-cutting concerns. Sometimes one has to do this several times to figure out which way is really fastest. Microoptimization things, like whether you used const somewhere or not, are much smaller details that have correspondingly small effects.
For code that one isn't specifically optimizing, speed probably doesn't matter. There was an exception to this, where we hit a little bit of a bump in the late-2000s on platforms with in-order CPUs like the PlayStation 3 and Xbox 360, because they have such a high penalty on cache misses; this tended to make general-purpose code slower and result in much flatter profiles. But now we are pretty much out of that era.
In general, const is more of a protection than an optimization. This is especially true heading into the massively parallel future, where const just sort of tells you whether some code is known for sure to run safely in parallel or not... and running safely in parallel matters tremendously more to overall speed than the number of instructions in that bit of code, or whatever. (Anyway, C++ is not at all a viable language in the massively parallel future... so that is going to be interesting.)
Constness of pointer or reference parameters has no optimisation effect because it doesn't convey any reliable information. It doesn't indicate whether the thing is written to in the body of the function (the compiler already has that information). It doesn't indicate whether the thing can be written to through another pointer (const parameters may be aliased). And it doesn't indicate whether the thing is written to through the same pointer by a callee (because the language contains const_cast).
Since the very thing that const is supposed to indicate is "no writes", and it doesn't do that, const annotations provide zero information. Thus they add no scope for optimisation.
constexpr isn't relevant to this issue. Since sane programs don't spend any appreciable time calculating constants, it's also rather uninteresting for the purpose of making programs run fast. As far as I can see its only practical purpose is to expand the set of fixed terms allowed as template arguments.
If / else is a way of defining what to be executed based on some condition.
The ternary operator is used to guarantee assignment to a variable based on some condition. It's not only a way to save space, it's a way to express your intent clearer. It simply doesn't leave any undefined code path behind.
In your case of changing the code a bit, in the carmack version of the function there's a risk that you remove the assignment to sides[i] in one case and it will later be undefined.
Though i agree that the syntax is horrendous since it requires you to remember exactly what the symbols ? and : do. Compare to the much nicer and to a newbie understandable python syntax: x = y if c else z
Basically it's a concise way to do a hard-coded table lookup. Tables are better for structurally regular code than if-else precisely because they restrict what that code can do.
The assignment sides[i] = ... with "? :" formatted as a table (in table notation) should be red as a single statement. It is a relatively common element, you will find in easy to read, once you see it a few hundred times.
My heart grew a little warm with the last paragraph of John Carmack's comment:
"The major evolution that is still going on for me is towards a more functional programming style, which involves unlearning a lot of old habits, and backing away from some OOP directions."
Here's a pretty trivial example in Haskell - computing the factorial function using a mutable variable.
import Control.Monad.ST
import Data.STRef
fact :: Int -> Int
fact n = runST (fact' n)
fact' :: Int -> ST s Int
fact' n = do a <- newSTRef 1
mapM_ (\x -> modifySTRef a (*x)) [1..n]
readSTRef a
Here the function `fact'` uses mutable variables (encoded in the use of `ST` in its type -- `ST` stands for State Thread) but the function `fact` is pure -- the call to `runST` ensures that none of the side effects leak out of `fact'`.
As with most Haskell code, the types are optional - I included them for clarity.
> As with most Haskell code, the types are optional - I included them for clarity.
I'd just like to make it clear to anyone else reading this. The types aren't optional, but because Haskell has type inference, specifying them is optional.
In Haskell there is the ST monad can be used to write stateful implementations for pure functions. The type system guarantees that side effects can't escape their scope.
Although this is great information I primarily had 'less pure' languages' in mind when I asked.
Come to think of it, restricting mutable access to only the relevant objects as per function call using const seems to allow just this!
A function having only const input and output "should be" free of side effects (if no global mutables); while still being allowed to run all manner of unpure processes local to its own calling context.
Clojure has "transient" (mutable) forms of its immutable datastructures, which can be bashed in place, but they're not allowed beyond function boundaries in either direction (I think). So you convert (copy?) to transient, mutate it in-place, then convert back to "persistent" before returning it.
He uses C# to provide examples of dynamic failure in compiler checked code. Since then, some of his 'what we want to write' pseudo-code examples are now reality in C#.
Do you have a link to the source for that? I'm curious to read.
If your description is accurate, Rust[1] is exactly what he wants. I've been learning Rust recently (with a little help from [2]) and it does a ton of static code analysis, is safe by default (no dangling or null pointers, no shared mutable state), uses Hindley-Milner type interface just like Haskell, and generally is what you would expect if Haskell and C++ had a baby.
Rust is pretty much exactly what a lot of people want and need; a high level language with a strong focus on safety and concurrency, while still allowing you to control the lower level details such as memory layout for good performance.
He sort of alluded to it, but there's the usual perceived tooling/ retraining/hiring / performance ceiling issues for shops with > N devs, N somewhere between 5 and 25 (I don't agree with his arguments, just repeating them)
Oh of course, it's far from trivial to switch to a new language. But if you're writing code you'll still be using in 10 years, the maintainability benefits of a safer language like Rust could be pretty huge. Also, Carmack switched from C to C++ not too long ago.
Has anyone here come across any particularly good books or other resources on functional style programming in C++? Especially ones that have been updated for C++11s new functional features.
As someone who has worked with the Doom 3 source code for a mod, I have the opposite opinion. The code very clearly shows a programming team (or programmer) in the process of transitioning from old-school C to C++.
Most functions have a huge blob of variable declarations right at the top, as was once necessary in C, even though these variables aren't used until later, or possibly even at all. Usage of const is minimal to non-existent. Global variables are everywhere.
It made some of the functions I had to modify so hard to read that I wound up completely editing them, particularly those variable-declaration blocks, even though I ultimately only needed to change a line or two to get my mod to work.
What do you find so disagreeable about collecting variables at the top of a function? For the most part, I like having all the variable declarations at the top, so it's easy to see what names are in what scope.
Finally got the silly regex. To align poor_style in vim with the tabularize plugin:
:Tab /\S\s\zs[ ]\S*
Explanation: \S\s* finds a non whitespace character followed by as many whitespace characters as possible. This brings us to the beginning of the variable name. We then use \zs which says that the "found" area should only begin here.
Since we want the * in block_style to be attached to the indented word but not before the variable name, we match either * or space followed by a non-whitespace character to symbolize the beginning of the word. End result:
void poor_style()
{
up_front declarations;
also encourage;
this_ridiculous *block_style;
that_is a_royal_pain;
to maintain;
because_some_long_type inevitably;
screws_it up;
}
Regarding poor_style(), I've never understood this objection (and indeed I prefer the block style you complain about there). Can't your editor fix this up for you in a few keystrokes? This is the sort of thing that an editor should make easy.
1. It wastes my time. Sure, I could probably set up my editor to fix this, but I shouldn't have to do so to satisfy someone else's pointless indentation fetish. I've personally never worked on a team where this was an accepted, general guideline. It was always just one guy who wanted this, and did it to every function he touched, adding maintenance headaches for everyone else (until/unless other people finally told him to stop).
2. It messes up diffs. Now instead of one line showing up in the diff, the entire block is often different. And yes, most diff tools have options to hide whitespace differences. Again, though, this adds overhead to everyone who doesn't want this block style. I'd rather not hide whitespace differences, because if someone has added a bunch of inappropriate whitespace (or mangled the block while trying to reformat it to include their new variable), I want to know about it during the code review so I can tell them to fix it then rather than finding it later when I'm editing the file.
3. It doesn't actually help anything. Yes, you get a nice column that shows you all the variable names. What good is that, though? Unless you're putting everything at the top of the function (which has its own set of problems), you're not really getting anything useful from this except maybe prettier code (arguable), because at a glance you still don't really get know all the in-scope variables (not to mention file-scope variables). Moreover, you actually lose something valuable with this style, because now it's harder to determine a variable's type. You're trying to scan left from the name across some indeterminate amount of whitespace to match with the type. This is not typically easy to do, which is why column-oriented data is typically displayed with alternating background colors on each row.
I bet that some programmers' brains need those orderly columns in order to be able to parse the code effectively, overwhelmed by the chaos of rivers of whitespace otherwise. In that case, it sounds like an editor that displays in columns but stores in compact form would be helpful.
That's certainly possible, and I agree with your idea that those devs should use the custom editor setup they need instead of expecting everyone else to accommodate them.
Your up fornt example isn't a result of poor style it's the result of a bad programmer. Assigning the right values to the right variables is the most basic of programming concepts. Sure typo's and bugs happen, I've done it too but it's still a programmer error not style error.
Any style that encourages errors is a bad style. Yes, it's possible to make the code correct and still use up-front declarations. It's also possible to make the code correct while using a 10k-line main() littered with gotos. It's still a very poor programming style.
People make mistakes. Practices should be built around this fact, not built assuming people could be perfect if they just tried a little harder.
Yes. I have absolutely seen bugs caused by the wrong variables being assigned to. I've seen it especially with loop variables. jaegerpicker indicates that he's also caused bugs by assigning to the wrong variables.
Not sure what you mean about "heavy constructor/destructor use" requiring this style.
Declaring at the top of a function conflicts with the C++ convention of powerful constructors. It's perfectly normal for a C++ class to allocate a lot of memory, open a network socket, or do any other number of heavy-weight things during construction. If you declare all variables at the top of the function, you're forced to have C-style init functions for all data structures that you call separately.
This is not JavaScript, where a variable defined inside a function always has the entire function as its scope, and so it's confusing not to have it on top. In C++, a variable is not in scope until it's declared, so you can just declare it when you're going to start using it.
If your function is five pages long, and in the fifth page you can't remember what's in scope and you have to reread through the entire function above, it might seem that having all variable declarations at the top would help, because you'd only have one place to look at. But the real problem there is that the function is too long, and it should be refactored instead.
Even in Javascript's case I don't like putting variables on top. Declaring variables only as they are needed lets JSHint warn me if I use accidentally something outside of its intended scope.
Among other things already mentioned, it makes it impossible to be "const pedantic" as Mr. Carmack professes to now be, i.e. declaring everything that is only initialized and then never changed as const.
I most certainly am const pedantic as well, and I was back when I worked on this project, too -- quite a few of the "inconsequential" lines I checked in were simply moving a variable declaration down to where it was initialized, and adding 'const'. I found the resulting code much easier to read.
If instead, a variable is assigned to only when it is defined, you're moving (in a small way) towards functional programming.
Also, I often use scope just control the lifetime of a resource. These look like meaningless braces in the middle of a function to the uninitiated. It's RAII.
I was reading '21st Century C' (I strongly recommend it for anyone writing C on a regular basis), and the author also argued against declaring all the variables up front. I don't quite know why, but I had a very strong reaction against it. As far as best practices go, I would try to keep every function small enough that you can find all the stack variables easily. If the function gets too hairy, refactor it so you can follow where the variables are going.
I don't see any advantages of declaring variables up front. Not all variables should be at the function scope. Often I have a local variable that is only used inside one branch of a conditional, why should I declare that at the top? Declaring things up front also separates the type from the usage, which makes things harder to read and takes up twice as many lines in some cases.
Maybe I miscommunicated; I'm not against declaring variables at scopes smaller than the function - I declare variables at the top of conditional branches as well. I still don't buy the 'twice as many lines' reason for mixing assignment and declaration.
You should be using const variables as much as possible (and in general preferring immutable data). In that case assignment and declaration _must_ take place at the same location.
There's an upvote button to agree with a comment, no need to post a single word. Generally, terse answers aren't really appreciated here (unless they're very good).
You can't do that unless you look at the assembly output. While there may have once been a time in history where "locally-scoped variable == entry on stack", those days are long gone. Any decently smart compiler (i.e. GCC and LLVM) will use registers in preference to the stack, and will collapse local variables whose lifetimes do not overlap into a single storage location.
My bad, I meant locally scoped, I tend to think in terms of "it's on the stack (or a register), or it's on the heap (because it's been malloc'd and needs to be freed)". I do need to review my terminology...
The only time you actually need to declare variables up front nowadays is if you have a goto that would otherwise skip over stack-allocated variable declarations. It's only a problem on certain compilers, but there are enough of them that it's best to keep variable declarations up front in such cases.
It makes it difficult to have const locals if you get some sort of dependency chain based on other locals interspersed with other computation. For example, if you're a const nazi you can't have code like this:
const int x = foo();
// ...
const int y = bar(x);
and also have your variables all declared at the top.
In C++ I'm a const nazi and declare where used to facilitate it, but if I'm writing C that targets the MSVC compiler (C89) where all variables have to be collected at the top of the inner scope I'll relax this as much as I have to.
I don't really know who the writer is, but Carmack's comment seems to indicate that it was very much a first pass at writing C++.
There's two things here that irk me a little bit:
the stream operator overloading - maybe I have been writing C++ for many years, but I can't get behind using printf vs. stringstream because << is a 'bastardization';
public variables because you don't like adding accessor/mutator methods.
Other than those two everything else is a stylistic preference that I mostly agree with. Statement braces around control blocks should absolutely be a requirement as well as pedantic const.
You might be correct, but I cannot say, having never read the source code. Since it was Carmack's first project in C++, I am inclined to believe you.
But would you agree that the practices mentioned in the article are what the team got right? You can remove the author's gushing about the code's beauty and have a substantive article left. For instance, the section on vertical spacing had me reconsidering my own style. The discussion of method signatures seemed pretty solid, too.
Personally, I found some of the horizontal spacing excessive. There were several lines that even forced me to scroll horizontally within VisualStudio (with a few side-panes open, admittedly), despite having a widescreen monitor. I'm pretty sure I chunked those up. Maybe Mr. Carmack has a much bigger monitor than I did.
Besides that, I think the stylistic changes this author is commenting on are fairly minor. I think the conciseness of vertical space was a bit confusing sometimes; this article only shows the simple functions where it works. The almost complete absence of templated is more likely because no STL code was used.
Certainly, the code base is very thorough. When I realized I had to use quaternions to represent the player's orientation in the game space, there was already an idQuat class ready and waiting and fully functional, which was very nice. I think that idQuat class was never even used anywhere in the base code, so it was cool -- for someone with who never knew about quaternions before then -- to have a class fully implemented and ready for me to use.
Yes, but it's easy to spend way too much time chasing that stuff, too. The API you're working with may not itself be const-clean, so you end up with a zillion const_cast expressions translating your "cleanly consted" local expressions into something the compiler will accept. And all that junk hurts maintainability and readability; you end up being tempted into nonsense like "caching" your non-const handle just to avoid all the casts, etc...
Broadly, my feeling is that getting your own APIs (internal and external) to be const correct is important and worth the trouble. But don't jump through hoops to shoehorn the strategy into someone else's code where it isn't honored.
I think that highlights a broader point that is touched on by the article. The Doom 3 code is not idiomatic C++ the way many would think of it but it does build its own consistent idioms and sticks with them wherever reasonable. It can do this through having a fairly small number of points where it touches the external world and ensuring that its idioms are compatible with that world.
If you're building a program in any language and it interacts heavily with a particular library then you'd better write something idiomatic to that library. If you're going to be using several libraries (including your language's standard lib) with different idioms then one of the most important design decisions you can make is how to bridge them, and where to make compromises.
Comments should be about why a piece of code does what it does not about what (should be clear from the function/method name) or how (should be clear from the code itself). As long as the comment just explains why it should be as long and detailed as necessary.
Mathematical thesis are using a syntax arguably much much more powerful than programming languages and yet they're still using lots and lots of english to describe what the formulas are doing and why they're (supposedly) correct in doing so.
I very much prefer to have 1000 lines of some Lisp dialect with lots of comments about what the code does than 10 000 lines of "self-explaining" Java/C# code.
I disagree. While the comments won't break your code, they can certainly ease the introduction of bugs, and cause cognitive delays when they're wrong or out of date.
Comments need as much care as the code itself, with the DRY principle guiding when they need to be pruned.
"I mistrusted templates for many years, and still use them with restraint, but I eventually decided I liked strong typing more than I disliked weird code in headers."
I never liked templates myself, and still avoid them almost completely. Even if my reasons are ridiculous, here they are:
1) I typically don't use anything where I don't understand its inner workings (i.e. how it manages memory, how the compiler is likely to optimize it). This does not mean the entity in question is bad, it just means I'm too lazy to learn about it on a deeper level.
2) In most cases where I need to handle a diverse amount of operations and a diverse amount of data types, it is not CPU-critical and I can resort to a higher level scripting language that is much better suited for the purpose.
3) Template syntax does not sit well with me. This is really just an OCD on my end.
4) I often prefer using uber-types (all-encompassing) to many different types -- within reason. I don't like to extend classes for this reason (particularly when you get into extension-hell with 5 different sub types).
I'd say 1-3 are perfectly reasonable. If you want to get good, put in the time and you will be rewarded, but that's really a choice.
#4... I think that might make a lot of sense with a weak or dynamic type system, but with a static type system there is a lot of benefit to having a different type anytime the behaviour is different. Of course, that can be tedious unless you have templates or at least generics...
The author seems to like the minimalistic comments. I wonder if the team looked back what their thoughts would be. I can barely look at code I wrote a year ago and not ask what the hell I was thinking, but in my mind it was absolutely clear at the time. I guess an impartial third party reading it and understanding it is a strong testimonial.
The key is to have self documenting code, not undocumented code. If you create functions that do only a single thing, with their purpose fully described by their method signature then you don't need comments - the method itself explains exactly what it does.
The author makes a good point that comments are just more text that you need to maintain, and whenever you make changes you now have to make changes in two places: in the code, and in the comment - (and I guess in the unit tests as well, depending on the change...)
The best code I've personally seen has been code with no comments and an attached document explaining how the system works and how modules tie together.
Oh, I get the concept. I've just been doing this for a while and see it fall part constantly. The problem is always that what is clear to you isn't clear to everyone else, including yourself somewhere down the road. And decomposing everything to atoms tends to lead to a mess of indirection.
Clarity can usually be addressed with longer variable or method names, but there's a strong culture against that in nearly ever programming community.
> Clarity can usually be addressed with longer variable or method names, but there's a strong culture against that in nearly ever programming community.
It would be interesting to see a list of those that discourage long method and variable names. I haven't seen anything that suggests that Java, Ruby, Python, Haskell, Kotlin, Scala, PHP, Groovy etc. discourage long variable and method names.
IntelliJ has inspections for long class and method names. A lot of Java devs run that. Long names are frequently derided in the Ruby community as being Java-like. I can't speak to Haskell at all. But generally whenever something needs to be typed frequently, it tends to be shortened. My favorite is when vowels are deemed too onerous.
While I was writing that, I realised that it may come down to the definition of what constitutes a long method / variable / class name.
I have usually found that the IntelliJ defaults are enough to make the names meaningful. For Ruby, the inspection kicks in at 30 characters. Interestingly, it's referenced from the ruby style guide here: https://github.com/bbatsov/ruby-style-guide where I can't find a recommended maximum length.
We're somewhat sidetracked from the main discussion. I'm generally in favour of trying to write code that is as readable as possible. Ideally in such a way that it is understandable even without the comments.
That said, I do think that good comments are helpful and essential if you're building a library. The Spring Framework comes to mind as a project that has a great set of documentation built from the comments (but also has very readable code, along with long method, variable and class names).
Haskell is a special beast, in the sense that it uses single letters a lot for generic types in signatures. Eg:
doFoo :: a -> a
where doFoo will take any type a and return something of the same type. Due to the density of the language, you'll often find plenty of small, commented functions.
Your verbose version is just the core of the function, not the type signature. I'm not really sure of what you want to demonstrate here. That short names are good ?
For me, external documentation is the absolute worst scenario. It takes me at least 4 times longer to read through and understand code without comments explaining in English what's going on. Here's a real-life example:
// Toggle between Dropdown and Text
if(_protected.fields[field].fieldType() === "Dropdown") {
_protected.fields[field].set("fieldType", "Text");
} else {
_protected.fields[field].set("fieldType", "Dropdown");
}
I think it would take me about 4 seconds to figure out that this code "toggles between Dropdown and Text" if the comment weren't there. Since the comment is there, I can just glance at the code and understand immediately what it does.
toggleDropdown(field) {
if ...
}
toggleDropdown(_protected.fields[field]);
Also the external documentation wouldn't have anything like this in it. It would be pretty much:
UI Code is in XXX. It communicates with ZZZ using YYY.
Fields in the UI are changed between text and dropdowns
depending on the value in the database that comes
from ZZZ. etc.
I guess Carmack would be a big fan of Rust which essentially borrows lots from Haskell, OCaml, C++ and Erlang while being native, safe and on-par in terms of speed with idiomatic C++.
I don't have much experience with C++ codebases, but is this really "exceptional beauty"? The majority of the things he comments on could be enforced with a code-formatter.
It just shows that the author is an intermediate-level programmer at best, if his main concern about the 'beauty' of code is in how it's formatted (spaces vs tabs! K&R vs Allman braces!) and whether it's const correct. Throw in a sprinkle of complaining about silly comments (because obviously there are armies of people out there defending documenting getter/setter functions...) and the obligatory 'templates are bad because I don't understand them', and you have yet another cookie cutter programmer link bait blog post polluting the general internets.
Astyle seems to have been around that long, I imagine pure C formatters have existed for much longer. They've got a lot in common with a language parser so if you've got a compiler you're most of the way there.
It would be really helpful to have some real world examples of changes going from C++ OOP to C++ functional and include the trade-offs.
By concrete I mean what changed in Id's code (or some other game or ui framework), and not just some text book example. What changed in GameObject or PhysicsSphereObject or RenderableSkinnedMesh or whatever things changed. What did the code used be like? What was changed? What benefits did the change provide? What problems did the change introduce?
Abstract talk is interesting but "show me the Money!" ;-)
These are very reasonable guidelines that most of us can probably relate to.
What I find much harder is to write "beautiful" code at a higher level. The examples shown are mostly algorithms working with fundamental language features. My code tends to get ugly when integrating APIs from different sources with different conventions. I spend a lot of time checking return codes, mapping from one set of error codes to another. Sometimes it's hard to decide whether a return code has to be checked or whether I should assume, for efficiency, that all parameters I'm sending in or getting out are ok.
Other things that uglify my code: exception handling, locks or other concurrency artifacts, retry loops.
I currently make a living by essentially applying these rules to other peoples code and handing it off.
My second programming lecturer ever, refused to correct my assignments if these rules were not followed. He never told us said rules though. This was before I learned what an array was! Got 0% on my first two assignments with him but eventually he corrected the next ones with a good life lesson.
Frankly its the only thing I remember from that asshole but was probably one of the most important lessons in my opinion!
I've always been told that the get, set() idiom is to allow the author to change the implementation at a later date.
I always resent doing it but I can see how if the body of the function is not declared in the header file, but instead the associated .cpp file, that an author can change it, without introducing a whole recompile overhead.
Writing code that may not be needed is bad, but it's a trade off vs. preventing other users writing code that depends on it when their code should not.
I'm kind of the same opinion as Carmack re: getters/setters. My feeling is, if all you're going to do is allow clients to read and write the variable, why not just expose it? Sure, you can argue encapsulation and even justify it by saying that later down the road you may want to change the implementation, but far too often I've seen C++ classes with a setter and getter for every variable, for no good reason (eg, they're never called, or shouldn't be). I think it behooves programmers to really think about the interfaces their code offers; don't just expose something through setters and getters because it's there, ask yourself, what is this class really offering that you couldn't get with a struct? Pass through setters and getters aren't an abstraction.
One (maybe stupid?) reason why I really like getter/setters has nothing to do with encapsulation, but that it makes it easier to search for places where a variable is changed. Often you have lots of getFoo and little setFoo functions - so just searching for "Foo" will return lots of results while "setFoo" helps me finding those faster.
Most editors with an indexer solves that automatically with "find all references". They can usually even order by "read occurrences" and "write occurrences"
Some arguments in favour of getters and setters are; that using a function allows for the addition of caching, addition of thread safety checks, changing to compute the variable rather than store it, addition of logging, mapping it to be generated from another variable, etc.
All of these are usually made by theoretical purists. Meanwhile, the people who write the code see that most variables don't need any of the above, most of the time.
I understand all of those justifications. When I mentioned changing implementation in my post, I was thinking of exactly those sorts of things. But none of them seem to be applied in the general case of "make a pass-through getter and setter for every member variable". My biggest question to the answer of universal getter/setters is "what is being abstracted?" Classes (and objects) are meant to abstract things like an "engine" so that you don't have to twiddle fuel_injector_rate, spark_plug_timing, etc, but rather just call engine.startIgnition().
I tend to not mind using getters and setters, but I don't create one for every internal variable in a class. I consider "universal" getters and setters (which I take to mean one getter and setter for every variable in every class) to be an absurd idea, like something taken to the logical extreme just to piss someone off.
Perhaps a lot of people don't like using or writing getters and setters because it's cumbersome if the only code in them is an assignment/return statement. Maybe something like providing Ruby's attr_reader/attr_writer/attr_accessor to create these boilerplate getters and setters is what is required. Also in Ruby all member variables are private, so methods are always needed to access them.
The argument from the Java camp is that if you always use the getters and setters, you leave the access and modification open to extension, say, by adding a callback listener for changes to a variable.
I then just say, change it when you need it, and use that fancy "find usages" thing most IDEs and vim have.
Precisely. The real reason Python gets this right is that it the properties feature lets you write `obj.foo` but still call a `getFoo` method behind the scenes.
For immutable objects, “setFoo()” makes no sense, and “getFoo()” is redundant. For encapsulated objects, “foo” doesn’t need to be exposed at all, not even through getters and setters. A group of fields with no behaviour is not an object—it’s data.
I am also a const nazi, and occasionally find myself trying to imitate some of its uses elegantly through run time errors or strict naming conventions in other languages. I do understand why many other languages decided not to support it though. There's definitely a few times I've coded myself into a corner and ended up with "const spaghetti", having to do a const_cast or two to free myself and make a deadline.
The article makes it sound a little like you can just slap const everywhere and your code will be better. It does take more time and effort to be const correct, although it's usually worth it.
A bit of a derail, but I have to ask: since Carmack has been big on rocketry for years now, is he in any way associated with Elon Musk's Space X program?
http://google-styleguide.googlecode.com/svn/trunk/cppguide.x...
The author's first point about establishing conventions so that you can re-use the code that works with those conventions is very important. At Google, nobody writes code to serialize/deserialize bytes, because the default answer is just "use protobufs". Nobody writes low-level communication protocols (well, outside of some very-specialized infrastructure teams), because there's one RPC system. There's one standardized naming system, and a mostly-standard logs format and method for analyzing logs. If you do batch computation, there're two solutions, and there're only a handful of different file formats and storage engines, certainly less than in the open-source world.
I think Rails and Django (and Lisp) discovered the same principle: if you get everybody writing their code & data files the same way, you can write tools to manipulate those files, and that saves you way more in productivity than trying to get the perfect file format.