
John Carmack's comment on Doom 3's code style - mohaps
http://kotaku.com/5975610/the-exceptional-beauty-of-doom-3s-source-code?post=56177550
======
nostrademons
A lot of the practices here are enshrined in the Google C++ styleguide:

[http://google-
styleguide.googlecode.com/svn/trunk/cppguide.x...](http://google-
styleguide.googlecode.com/svn/trunk/cppguide.xml)

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.

~~~
zem
> A lot of the practices here are enshrined in the Google C++ styleguide:

with the notable exception of lining up things horizontally, which tends to be
frowned on.

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

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

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

~~~
0x0
Sounds like someone should implement a blame option for "ignore whitespace -
show latest author with non-whitespace changes"!

~~~
projct
git blame -w already does this:
[http://www.kernel.org/pub/software/scm/git/docs/git-
blame.ht...](http://www.kernel.org/pub/software/scm/git/docs/git-blame.html)

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

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

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

------
dchichkov
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:

    
    
      for ( i = 0; i < in->numVerts ; i++ ) {
          dot = plane.Distance( in->verts[i] );
          dists[i] = dot;
          if ( dot < -LIGHT_CLIP_EPSILON ) {
              sides[i] = SIDE_BACK;
          } else if ( dot > LIGHT_CLIP_EPSILON ) {
              sides[i] = SIDE_FRONT;
          } else {
              sides[i]  = SIDE_ON;
          }
          counts[sides[i]]++;
      }
    

Versus:

    
    
      for(i = 0; i < in->numVerts; i++)
      {
          dot = plane.Distance(in->verts[i]);
          
          dists[i] = dot;
          sides[i] = dot < -LIGHT_CLIP_EPSILON  ?  SIDE_BACK  :
                     dot > LIGHT_CLIP_EPSILON   ?  SIDE_FRONT :
                                                   SIDE_ON;
    
          counts[sides[i]]++;
      }

~~~
sgift
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. :(

Conclusion: We have different preferences.

~~~
brooksbp
the ternary operator is awesome. it should be used more often.

~~~
neutronicus
Yeah, I really like it.

It's a nice little oasis of Functional style in an otherwise pretty un-
Functional language.

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

~~~
quux
John Carmack wrote a nice article about experiences writing functional code in
C++ here:

[http://www.altdevblogaday.com/2012/04/26/functional-
programm...](http://www.altdevblogaday.com/2012/04/26/functional-programming-
in-c/)

~~~
worldsayshi
"a function can still be pure even if it calls impure functions, as long as
the side effects don’t escape the outer function"

This is a very good point that probably could be systematically exploited.
Does anyone know examples of this?

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

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

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

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

~~~
dpark

      void up_front_decls()
      {
        float some_var;
        int another_var;
        
        some_var = get_some_var();
        do_some_calculations(some_var);
        maybe_something_else(&some_var);
        
        some_var = get_another_var();
        do_some_other_calculations(another_var);
        blah_blah_already_broken();
      }
    
      void as_needed_decls()
      {
        float some_var = get_some_var();
        do_some_calculations(some_var);
        maybe_something_else(&some_var);
        
        int some_var = get_another_var();
        // compile-time error
        // ...
      }
    
    

Also:

    
    
      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;
      }

~~~
wtracy
I would argue that if declaring up-front versus declaring as-needed makes a
significant difference in readability, then your functions are too long.

~~~
nostrademons
Except for loop counters. Being able to do:

    
    
      for (int i = 0; i < len; ++i) { ... }
    

vs.

    
    
      int i;
      // Half a dozen lines
      for (i = 0; i < len; ++i) { ... }
    

makes a big difference to me.

------
melling
"I am a full const nazi nowadays, and I chide any programmer that doesn’t
const every variable and parameter that can be."

Immutability...one less thing to worry about.

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

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

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

~~~
martinced
That is so wrong.

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.

Code can contain bugs. Comments cannot.

~~~
thinkstoomuch
>Comments cannot.

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.

------
gavanwoolery
"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).

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

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

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

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

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

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

~~~
scanr
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).

~~~
nirvdrum
I can get on board with common sense decision making :-)

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

~~~
pjmlp
On the Quakecon where he talks about static code analysis, he mentions that ML
languages are too much for games developers, given the usual skill set.

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

~~~
zxcdw
Considering the codebase is around a decade old, how many code formatters
existed for C/C++ back then?

I am asking because I've only used gofmt to format Go code and infact it was
gofmt which introduced the whole concept to me.

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

<http://astyle.sourceforge.net/notes.html>

I think Go is pretty unusual in that gofmt is provided with the language. So
all Go everywhere looks the same. It's awesome isn't it? :)

------
greggman
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!" ;-)

~~~
groby_b
Go search for "data oriented programming". Mike Acton wrote a few interesting
presos on it, GameDevADay blog has a few others, too.

Yes, technically they have different motivations for going the way they go,
but the end results tend to coincide neatly :)

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

------
Snowda
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!

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

------
dysoco
So he discourages Getters/Setters and instead says that declaring the variable
as Public is better?

I mean, isn't that like not giving a sh-- about encapsulation principles ?

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

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

~~~
Too
Most editors with an indexer solves that automatically with _"find all
references"_. They can usually even order by "read occurrences" and "write
occurrences"

~~~
coffeeaddicted
VS2010 and C::B both don't seem to allow that ordering. So not a solution for
me, but certainly could be other IDE's support that.

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

~~~
coffeeaddicted
I also care about const-correctness... but once in a while in a dark hour I
wonder if I really saved or lost more time by it so far.

------
hresult
I don't like this coding style at all. There are quite a few inconsistency
with respect to the formatting, for example:

common->Frame(){ session->Frame() { ... } }

etc.

------
rmangi
Great article. Even if you're not a C/C++ programmer.

------
thoughtcriminal
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?

