Okay, my name's Robert Ramey.
I am a
Boost library author.
And the title of my talk today
is Writing Effective
Documentation for C++ Libraries.
I'm pleased to see as
many people here as I see.
It's a topic that nobody
really wants to address,
and there's a good reason for that.
It's,
let's see,
it's a pain to write,
it's unpleasant to write,
it's a task that nobody wants to do,
it takes a lot of time,
it's kind of time-consuming,
it's not appreciated,
and the worst part is,
that's the worst part that
nobody's happy with it.
A recent survey on
Github concluded that
93% of people reported
being frustrated with quote
"incomplete or confusing documentation".
To me, that's an incredible number.
It means that a huge number of people
are investing a huge amount of work that
a lot of people don't
think is very helpful.
Now the upside of this is, you
can do a really bad job at it
and you won't get fired.
I mean, that's the proof.
So,
but the main thing, and the
thrust of my talk today is that
I think that this problem
holds back our development,
it keeps us from making better stuff,
and so not only is it unpleasant,
it's counterproductive.
And what we want
is documentation which is easier to write,
it's less tedious, less time-consuming,
all the stuff that it actually is,
we want the 180 degrees
different from that.
We want it helpful to users and
helpful to other developers.
I don't think there's any
disagreement about that.
So,
the question is how do we fix this?
As programmers, to a man
with a hammer his hand,
the whole world looks like a nail.
To us, immediately we say,
you know what we really
need is a better tool
that writes the documentation for us.
And we have tools,
and there's always
disputes about the tool.
And there's always, in the tool thing,
there's always more hype and all this.
The fact is, we still got the
93% of people who are saying
the documentation isn't working for them,
in spite of the tools we have.
Better tools are not gonna help us.
We have to step back and
think, wait a minute,
it's not the tools, it's something else.
The tools do have something going for us.
They do create, oftentimes,
very nice-looking documentation,
and a lot of times, it's really,
it saves a lot of work because
one particular favorite tool
and probably others glean information
from looking at the header files,
or the function prototypes or whatever,
and then take that information
and present it in a way
that looks like documentation.
It's seductive, it's attractive,
users don't find it particularly
helpful, and why not?
Well, it doesn't really
add any information
which is not in the headers already.
So if we do it that, if
we go down that route,
we're gonna find that
we're gonna be producing
good-looking documentation
which still suffers from all
the problems and disappointments
that we already have.
So here's how we go about doing something.
We get a task from somebody,
either a boss or team leader,
or we make it up ourselves or whatever,
and we start to code, because
that's what we like to do.
And we'll go through a couple
prototypes and try this
and we'll look for libraries,
and we'll do what we do.
When we got something that we
think looks like it might work
then we'll write a test.
Well, maybe we'll write a test because
a lot of times, we don't,
because that's another tedious job.
But oftentimes, well, we
might write a small example
just for one reason, at
least on the IDE I use,
I can't compile a header
file without a CPP file,
so it actually forces me
to make a little CPP file
with, at least, with an
include and a main in it.
But oftentimes,
even before drinking the Kool-Aid here,
I would be inclined to make a small test
to see if the thing does
what I think it does.
It never works the first time,
or it rarely works the first time,
so this little test or a little example
really ratifies what I'm trying to do.
And so, and after we go
back and forth on this
for quite a while, then we'll start
writing the documentation,
and when we do that,
we're kind of amazed and disappointed.
Of course now, we've
already told the boss,
wherever it's done, all I got to do
is just write up a little
document, and he says great,
here's the next job.
Start tomorrow, because
you can do the doc today,
and then we discover when we
try and write the documentation
it's really hard because
we can't really describe
exactly the way, what we
did, or it starts out well,
and then we get to a spot
and then, wait a minute,
you call this function
but the output depends on
something else over here,
which is set by another function
or depends on this type.
Oh, actually I have to
include that information too,
otherwise the person who uses this thing
won't be able to know
under what circumstances
this call is gonna give what answer.
And of course if you
step back just a second
and think you know, actually,
we should make some changes in the code.
How many people have come
across that situation?
Okay, that's fine, we've all done it,
because it's impossible to avoid.
When you're writing code, you're
not really thinking about,
you've already, you've gone
below the original abstraction,
now you're into the code.
At that point, when you
break the abstraction,
where the original
abstraction isn't correct,
now you don't see it
until your code is done.
So it's too late to fix this stuff,
you already told people it's done,
you already put it out there
for people to try, and now,
you're in a box,
you either have to take on
more work or just sign it off
and what are we gonna do?
We know what we're gonna do,
we're gonna sign it off because
we got other stuff to do.
And then of course, we end up
in that 97% because that's,
but as I say, good part is we keep our job
and we get a raise, 'cause we got it done.
And because nobody
really actually verifies
whether the users are
really happy with it.
If I sound cynical,
well, it's actually true.
Anyway, so, I'm going to,
now I'm gonna give some
positive, constructive,
and useful advice.
I think I put my finger
on what the problem is.
Now, how do we get around that?
The thrust of this idea is
we write the documentation
in parallel to when
we're writing the code.
This has a couple great benefits.
One is it stretches out
the job in little pieces,
so it's not faced with a job
we got a week to sit down
and look out the window
and stare at this stuff
and then type some more
and then become frustrated,
no, we just do it in little bits.
It's much more than that
but if it were only that,
it would still be quite helpful.
So we're gonna write the
first sentence of this doc,
it's just one sentence.
Sure, if we've been given a task,
we can sit down and write
one sentence what this module
or what this function or what
this type is going to do,
that should be pretty simple.
So that's what I recommend doing,
writing one sentence, maybe two.
I think most of the time
it can be done within,
you go back and take it to the boss,
okay, you're signing off on this,
this is what we want, or your team leader
or all other people that
are gonna use this thing,
and they'll say yeah, sure.
Well this, you'd be surprised
how many times they don't say that.
You'd be surprised how many times
I'm talking with somebody else,
and we have a totally different idea
of what this thing is supposed to do.
Just writing this one sentence
and coming to agreement on it
is a huge step.
Now here's a couple examples,
this is just fun here.
Here's an example from a
library that was proposed
for Boost a year ago or whatever.
I'm gonna read a little
bit of it just so you can,
just so you can cringe.
xxx is a C++ library
which lets you schedule
an order dependency graph of
file input/output operations
to be executed asynchronously
to the maximum capacity
of your hardware.
If you want to do portable
asynchronous i/o in C++,
especially if you need to easily consider
order issues of reads and writes,
this is the correct library to be using,
yada yada yada.
Okay, I mean right away,
I'm reading that, I'm done.
If I'm looking that I have to
go through 20 pages of this,
I'm finished.
And so, that's an easy one,
because as I say, I'm done.
And I'm gonna give an example of,
I read, I did take the time to read this,
it was kind of painful,
and it was kind of time-consuming,
and I said if this gentleman
had written the following,
I think that we would have been
off in a much better place.
Now it turned out that that library
wasn't accepted into Boost
in large part, I think,
because of the previous page was
symptomatic of the whole thing.
Nobody could really feel confident
they knew what it was supposed to do.
So that's a pretty easy one,
but now let's look at some libraries
that have been accepted into Boost.
Now we're gonna switch the thing around,
and this is just for fun,
I could skip this part,
but it's too much fun to skip.
So I picked a number of libraries
from the Boost library set
and I just picked the first
sentence or first paragraph
from the library documentation.
And I'm gonna state it here
and I'm gonna ask you guys to guess
which library you think it might be.
So this library provides,
yeah, what is this, Jeopardy?
I guess this would be Library
Jeopardy, wouldn't it?
(audience laughing)
So the C++ library provides
functions, classes, templates,
traits, and macros for
the control, inspection,
and diagnostic of memory alignment.
Who has a guess?
This is the easiest one.
Come, come, come.
This gentleman, you have
to have a guess here.
You have no idea?
Okay.
I'm disappointed, I'm disappointed,
but I'm gonna help you out,
at least for the first one.
Boost Align.
It seems obvious in retrospect, yeah?
And actually,
this particular sentence I
think is actually an exception
to my case, I think this is
actually a very good summary
and description of what Boost Align does.
So this guy, who's the author of this?
Normally, I'm not gonna name names.
Glen Fernandes.
He's been in Boost for quite a while.
I think he did a great job with that,
and though I don't know for a fact,
but just reading that suggests to me that
it's gonna be smooth sailing
if I start to use this library.
Yes?
- [Audience Member] Yes, a question.
I'd eliminate functions,
classes, templates,
traits and macros.
That's like, every C++
library provides that.
Why you should include this?
That's my question.
Well, that's an interesting question.
You could, what you're telling
me is, this is understandable
but they actually could
have made it shorter, right?
You could have said this C++
library provides facilities
for the control, inspection and diagnostic
of memory alignment.
Would have been an improvement.
Even a good one get improved,
this is actually better
than like 98%, but it's,
but even then, you could
still do it better.
And you're right, it would
have made an improvement,
it's good enough as it
is, but you're right,
it would have made an improvement.
Okay, now you got, now we're warming up.
Let's see if we can get
somebody to stick out
your hand up this time.
There are times when a generic,
in the sense of general,
opposed to template-based
programming type is needed:
Variables which are truly
variable accommodating values
of many other more specific types,
rather than C++'s normal
strict and static types.
We can distinguish these
basic types of a generic type.
Well, the only thing that's missing here
is iambic pentameter.
(audience laughing)
So, let's have a look.
This gentleman suggests Boost Any.
Boost Any is one.
Hana, Variant.
Okay, how many are gonna
vote for Boost Any?
(audience speaking excitedly)
Oh, I ruined it.
Oh, I ruined it.
(audience laughing)
Boost Any.
Okay, this, frankly, you're very astute.
Have you used Boost Any?
Okay, well I'm very impressed
because that particular
introduction was totally
opaque to me, I have to say.
But now, listen to this.
This is, the gentleman who
wrote this, Kevlin Henney,
is a very well-known author.
And for whatever reason,
when he got into in,
and very successful, and a great speaker.
So when your own stuff looks like this,
you don't have to feel that
bad because it's apparently,
it's apparently hard to avoid.
Okay.
All right.
I just had two of these to go,
and I would have done
less, but it's so much fun.
The Standard Template Library,
the C++ Standard provides a framework
for processing algorithms on a
different kind of containers.
However, ordinary arrays
don't provide the interface
of STL containers, although
they provide iterator interface
to STL containers.
As a replacement for ordinary arrays,
STL provides std::vector.
However, std::vector provides semantics
for dynamic arrays.
Thus, it manages data to be able to change
the number of elements,
et cetera, et cetera.
Okay, this gentleman.
- [Audience Member] Array.
Array.
How many people agree with Array?
Okay, anybody have any other bids?
- [Audience Member] Container.
Container.
Anybody buy onto container?
Okay, Array is carrying the day.
And let's see what it is.
It's Array, okay.
- [Audience Member] Are they sorted?
Yeah, well, I said--
(audience laughing)
Wait a minute, wait a minute here,
that's a very interesting question.
I said I'm just gonna pick off
some random Boost libraries,
and what I did, I just looked at the list
and picked the top X number.
(audience laughing)
So I think I was, I don't know,
I was in a hurry and I just said well,
let's just start looking, oh,
and just the first group
turned out to be a good sample.
So I ruined it,
and I just can't get anything
over on this audience.
It's very stressful giving a
talk in a environment like this
I have to say.
Okay, I think this is the last one.
It's a cross--
(audience laughing)
Okay, do I have to even read it?
This is actually so short.
I'm just, give me a chance
to read this short one.
This is easy.
It's a cross-platform
C++ library for network
and low-level I/O programming
that provides developers with a consistent
asynchronous model using
a modern C++ approach.
Everybody feels they got
this, raise their hand.
Okay, you probably got it.
Okay, what is it?
- [Audience Member] Asio.
Asio, right, exactly.
This is a beautiful job.
And by the way, I don't think it's a,
if this is a sample of the whole library
and its documentation,
it's no surprise that
it's very successful.
So I think that I can
get a sense oftentimes
of the quality of a library
from the very first sentence
of the documentation.
Okay, so I'm gonna
conjure up a new library
and I'm gonna call it RingView.
There's a proposal for,
I think that just the name RingView,
I think most of you should
have in mind what it is,
or at least what it is in in your own mind
what a RingView class would be.
I'm gonna presume that, I don't
see too many people looking
with puzzled looks, but in any case,
there is such a component being proposed
for the standard library.
They call it, well, they
they call it RingSpan,
but in any case, the first
sentence from that proposal is
this proposal introduces a
ring to the standard library
operating on a span, named ring_span.
The ring_span offers similar
facilities to std::queue
with an additional feature
of storing the elements
in a continuous memory
of being of a fixed size.
Well, I have a couple problems with this.
First, it's tautological,
it defines ring span
in terms of the noun ring,
and that's very odd to me.
I'm not sure where there, it doesn't,
it could be a mathematical ring,
it could be like a wedding ring,
so I don't think that adds much.
It also presumes an underlying
continuous memory container,
which is not what I expect to see at all.
Now I'm already criticizing the proposal,
I've only read the first sentence.
It goes on in this vein for 14 pages.
That's another thing
that I'm not crazy about.
I'm not gonna display it here.
I don't think it's necessary,
unless you guys like to
enjoy reading standards proposal,
but I just use that for reference because
this RingView class that we're
gonna make and describe here
does the same thing or
does a similar function.
So here's the sentence that I made up.
A ring_view is an adapter
which provides functions
for first in/first out
access to existing storage.
Everybody okay with that,
or somebody want to do a better job?
Oh, come on.
I had to do it like five times.
Go ahead.
- [Audience Member] Maybe
replace the word functions with,
like C++ functions, so
function is like facility.
Well it's, I'm referring
to C++ functions and it,
excuse me?
- [Audience Member] That was not obvious.
Okay.
- [Audience Member] Eliminate
which provides function,
just is an adaptor for
first in/first out access
to existing storage.
Eliminate what?
Provides--
- [Audience Member] The
ring_view is an adaptor
for first in/first out
access to existing--
Okay, yeah, that would fly.
That would be okay.
That would probably be
a minor improvement.
As I say,
I actually, this is the fifth attempt
that I actually did this,
and the first one was twice as long.
This is very, this is extremely
common, but it's very,
it's very helpful to
clarify your own thoughts.
Okay, and so once I got that point,
next thing I do is I'd
start to write some code.
I don't, I'm not really
going big on specifications
and all this, yada yada yada,
I mean if you have to
do it, maybe it's useful
and maybe in something really
complicated it's useful,
but the very first
sentence to me it gives,
at least in my mind and I think
in the audience that I'm referring to
a fairly good idea what
we're talking about.
So I make a little demo here,
I make a little example.
I include my header.
And I define some storage,
which is 42 characters long.
And then I construct a ring_view,
which has an iterator to the
beginning of the storage,
an iterator to the end of the storage,
and I'm already thinking, or
it should be fairly obvious,
I defined a function called
push, which appends characters.
I defined a function called
pop, which retrieves characters.
And I have some checking
in there to make sure
that I don't push more than 42
characters in, in this case,
and I don't retrieve more characters
than have been pushed in.
So I think that just
about anybody in this room
would consider this a pretty trivial
and self-explanatory example.
And I did include comments in here because
I know I'm gonna be including
in the documentation,
and rather than, and
it's frankly just easier
to put the comments right
here next to the code
than it is to put it
in some narrative apart
and refer to it, so I'm
already thinking about
how this thing's gonna be documented.
Anybody have any corrections
to this particular piece of code?
- [Audience Member] It
would be nice to see
what actually was displayed in it.
Yeah, this is the code.
And the documentation
will show the output.
We're not there yet, right?
I'm just showing you what I wrote so far,
I haven't written any documentation yet.
You're already ahead of me.
Yes, Peter?
- [Peter] In a test, it
would actually show what the
expected result where in code, directly.
Yeah, you would.
And I haven't written a test.
But if I get around to writing a test,
I'll keep that in mind.
- [Peter] Why don't you
start with a test up front?
Well, that's an interesting question.
This is an example.
It's really meant to expose,
and has a didactic purpose.
It does test the code, but
it's really not the purpose
of this little module here.
So I think I do make a distinction
between writing the test
to test things and writing
these little examples
to illustrate your code.
They do, there is some overlap,
but I think trying to
include the facility of test
in explanatory example overloads
the example with too much.
- [Peter] I object.
Okay, your objection is noted.
(audience laughing)
We like people who object and
are willing to accept the fact
that it's noted so we can move on.
(audience laughing)
Okay, and so now,
since I'm really hot to write my code,
and I woke up last
night thinking about it,
and here's the code that I wrote for this.
I am gonna confess this
was not the first shot,
I did have to run it a
few times to make it work.
So I
(coughing)
I import the iterator headers,
I import the standard exception header.
We know in our example,
it did throw an exception
if there was a problem.
I included assert, which
I see now, I don't use.
And I include a header utility
which includes the description
of the forward iterator type.
Yes?
- [Audience Member] If you
have to import from tick,
why do you have to comment
that this is a tick library?
I am, I'm going to talk to,
okay, well, I'm gonna
explain it to you now.
You're probably correct.
Well, how should I say?
Actually not because
if I'm including this,
that would be some other
documentation would explain
what that is, but maybe
it would be helpful.
But I'm gonna give it,
I'm gonna include it here,
the explanation verbally,
and just accept that
somebody might think that
they would like to see the writing.
So the first line I have
here is the class name
and it takes a parameter of
which I've denoted by an I.
That parameter is an iterator.
And we see a little bit farther down,
we're constructing a ring_view
with a pair of iterators,
clearly at the beginning
in the end of the storage
which we've already allocated.
Now at the very top of this,
I have this thing that you
might not be familiar with.
It's called, it says TICK_TRAIT_CHECK,
and then it says is_forward_iterator.
This is a type trait which
will stop the compiler,
and throw a, will invoke a compiler error
if the parameter we pass to this ring_view
is not a forward iterator.
It's very, one could easily,
one of your users could easily say oh,
I just passed the storage,
it's a ring_view vector.
That's not what this accepts.
C++, as things stand today,
does not have the ability
to check the features
of a template parameter.
And--
- [Peter] What version of
C++ are you talking about?
What?
- [Peter] What version of C++--
Well, now see,
now we're getting into the weeds.
For the sake of the argument right now,
it depends which compiler your version,
which compiler version,
which C++ version, et cetera.
Up until this point,
up until very recently,
I can put it that way, there really was no
natural way of checking these parameters.
There was the Boost
concept checking library,
which even though it's been
around for, oh, 15 years,
or maybe less, was hardly ever used,
I don't think I ever saw it used,
and with C++ 11, we now have
the ability to implement
the checking of type parameters,
and this library, which
has been submitted to
the Boost library incubator,
it's written by Paul Fultz,
basically permits one
to include this macro,
or macros like it and
verify that a type parameter
fulfills the requirements that we need
to make this ring_view work.
So what this is is basically
it checks this parameter I,
to make sure that it's a valid iterator.
That's what it does.
Now coming down the pike,
and everybody's got a
lot to say about this,
as a practical matter I have never seen
anybody's code but my own
actually check the parameter type.
I'm sure there's some out there.
Bjarne has made a big deal and whole talks
about how you should be using concepts,
which is what this is about.
But currently, at least
when he made that talk,
the only way to do that was to use
GCC compiler version such and
such with the requires clause.
- [Peter] We have static
assert in the language
since C++ 11, we have
corresponding traits,
and with static assert, and whatever--
Right, and this library uses,
this tick library uses this facility
to provide an easy way of specifying.
- [Peter] So what you would code
to compile it to C++?
Excuse me?
I was asking--
Oh, this would, this particular library
would require C++ 11 or later.
And if you wanted to run this
on C++ earlier than that,
you just have to comment
out that statement
and hold your breath,
because typically speaking,
libraries did not check
parameters, now they can.
Still, the syntax is not real natural.
The people that made the
syntax think it's natural.
I don't think it's particularly natural.
This gentleman's made a library
which makes it pretty natural,
and I found it to be
useful for this context.
But what we're talking about
here is the documentation,
and this tells me right away,
if I want to put this in to
make sure this is used properly
then the documentation is also
gonna have to specify that
the parameter for this ring_view
better be a forward iterator.
So in this sense, we're
keeping the two in sync.
And that's kind of the,
the rest of this is pretty
straightforward, as you'd expect.
It's got the the functions
listed, there's only a few.
For purposes of illustration,
and it's actually kind of convenient,
I put the public interface
to this type at the top
and all the private stuff below.
I didn't include any of
the implementation stuff.
I want to emphasize, it's
my belief that it's helpful
if we make a clear distinction
between the user interface
and the implementation.
And by separating the public API
from the implementation in
this particular example,
it's my way of,
what's the word?
Of emphasizing that point.
I think that when we create documentation,
one of the problems is
we put too much in there.
I think that we should make
a clear distinction between
the user of this class
and the person who's gonna
maintain it or whatever,
the person who's gonna
maintain it or make it faster
or find a bug, he's gonna be
looking at the code anyway.
All the information that really specifies
the details of how it's implemented
can be easily found there,
they can be commented there,
the person who's using
the class doesn't need,
and frankly doesn't care at
his level about those aspects.
So I've tried to structure
this in such a way that it,
it emphasizes that distinction between
the user interface and the other.
So, now's a good, anybody
have any questions?
I'm hoping that this should
look pretty familiar.
Okay.
Oh, and by the way,
the class is a pretty cool little class,
if I may say so myself.
I've used it, I needed this
facility a bunch of times.
There's a ton of them on Github.
There's the standards proposal.
They all look more
complicated to me than this.
But anyway, that's an aside.
So here's what the
documentation for this thing
should look like.
You should have a introduction,
purpose of library, and we
boil it down to a sentence,
an even shorter sentence than I proposed.
A couple, one or two or
whatever motivating examples.
This is a very simple
component we're making.
One example is plenty to
illustrate how to use it.
And then we'll have some notes,
and then we'll have kind
of weird stuff like,
I'm not sure, stuff that
doesn't fit anywhere else.
And sometimes you have,
well, I don't know,
now you might refer to
the standards proposal
in there or whatever, but as things go on,
as a practical matter,
you'll end up with something
that doesn't fit anywhere.
Well, that's where it fits.
Then there's a section, that rationale.
I'm not sure, I first saw
this in Boost libraries.
I had never seen it before.
And this actually is a, I
find it to be very useful.
As I'm coding, I discover little quirks
that I decide to do
something in a particular way
because I discovered something,
like this doesn't work on that machine,
or whoa, if I do that, it
creates another problem.
And then I just put those,
that list of gotchas,
I just put in this
section called rationale,
and I just number them.
This thing returns
an R value.
Well actually, that's too
obvious to even put there,
but it turns out you have
some miscellaneous stuff,
some reasons that you
did something so that
five months later, some
guy comes and tells you,
oh, you should have done that.
Of course he's,
that's a drive-by review,
he's just spitballing you,
because he hasn't even
looked at it, but now,
you have a reason why you did it that way.
It's very helpful and
it keeps you from doing
the thing again and again.
It's also simple to do.
And then we have at this point,
we have the reference section.
Reference section is gonna
be pretty simple because
it's almost like a form-filling
exercise at this point.
So, let's see.
It consists of four parts.
Most documents probably,
most types and whatever
won't have all four, but
one type is a concept.
I call it type requirements.
I believe the word concept has been
unfortunately very misleading,
and we've been suffering
from it for 20 years.
Basically it's every type that we use,
every parameter type that
we use needs a description
of what that parameter type has to do
in order to be useful in the
context of our ring_view.
It turns out we almost never
have to write such a page
because those type requirements
are all grouped into names
in the standards documentation.
We've got default constructible,
we have move assignable,
we have forward iterator,
we have a sequence,
we have et cetera, et cetera.
So most of the time,
those pages don't even have to be written,
but it is useful to refer to
them and in the documentation,
I will often find a,
say the ring buffer
requires a forward iterator,
I won't include the concept page there
but I will have a link
to the SGI document or something like that
that includes it.
So at this point, we've only got one type.
We've got no concept we're defining.
We don't have any free
functions being defined.
And we're not having any meta functions.
So our case is gonna be,
my example case isn't really rich enough
to describe all the
problems we could have,
but it doesn't matter,
I'm gonna keep going here.
The standards document,
kind of to my surprise,
actually describes how to describe stuff.
Unfortunately, it's not
well it very well-described.
So you can look at it.
One thing that is interesting about it
is it makes a special point of saying
that when we're talking about a class
that we should describe things
in terms of valid expressions
as opposed to class members.
And they say that specifically here.
That strikes me, always
struck me as kind of odd.
And actually with, I became used to it
and I see the merit in it,
I see the necessity of it,
and I think that actually
what the custom is today is
that if we describe a concept,
we describe valid expressions,
and if we describe a class,
we describe class members.
I think we would be better
off today if we used
the valid expression method
for describing class behavior
as well as concepts.
Later in,
what can I do for you, Peter?
- [Peter] I would like to comment,
the standard is never
intended as a tutorial
of how to use a feature.
It's a good thing, too.
They were successful.
- [Peter] And it also opens to plenty
of possible implementations
of library feature,
which make you write your own library,
you just have one implementation.
Well, the standard touched upon,
I forget, they talk about
method of exposition.
I would probably argue that that's outside
the scope of the standard.
But anyway, they did have
that little paragraph in there
that I thought that was interesting.
And the other, another
document which is, to me,
I'm gonna call it the,
to me, it's the Bible of how to do this.
This is written in the
late 90s, and it's got SGI,
Silicon Graphics logo on it.
I'm guessing it was written
by Alexander Stepanov,
but I could be wrong.
It's unfortunate they
don't have an author.
And they describe here
that this is the the best,
most succinct description of how these
three different components
should be formatted and what
information they conclude.
He describes, I'm not going to
go through it in detail here,
'cause you can look it up
yourself, but when you,
after this, next time you
have this problem, go here,
and this is where you'll
find the real explanation.
It talks about how to use,
well, it talks about first
concepts and how they're used,
how to describe them, valid expressions.
Then he describes another
page which is a type page,
he gives examples for that,
it has all the sections,
and then a function page.
This is really in a nutshell,
the rest I'm gonna talk about
is really just examples of this.
This is the thing you have to
get to know when you're lost,
and here in the SGI documentation,
here's some sample pages.
This is what a forward
iterator looks like,
it has a short description,
it's pretty good.
Excuse me.
It has another interesting
thing which is a refinement of,
a forward iterator is
everything an input iterator is,
plus a couple of other things.
So there's an inheritance
relationship going on here.
So in the forward iterator,
they don't describe all
the stuff that it does,
they just describe what the
forward iterator adds to
the input iterator.
So you'll find that these
things are incredibly short.
They have a little thing with
notation, valid expressions.
These two tables, I just
consolidate them into one
because it saves space.
And that's pretty much it.
And they describe as examples a couple,
a couple of real classes that model
this concept as described.
We're gonna actually use
this forward iterator.
Our documentation is
gonna just point to this
so that when a person needs to know
what kind of parameter he
can put into a ring_view, ah,
he's got it right here,
and I didn't have to write a word,
I just had to put in a link.
Question?
Sure.
- [Audience Member] How do
you think it helps the content
that he specified the audience
at the very beginning?
I don't know if you saw that,
he specified a very
sophisticated audience.
I'm having a hard time following sometimes
because I'm a confounding audience.
I think that for regular C++ developers
that may not be sophisticated,
I think this assumes their
familiarity with C++,
especially with the templates that,
I'm just wondering,
how do you think that helps the document?
Well, why, let me ask the question back,
see if I got it right.
What part are we looking at?
It assumes a general familiarity of C++,
especially with C++ templates?
Yes.
Okay.
Additionally, you should read
introduction to the
standard template library
before proceeding.
I think that's actually,
I don't know if it belongs in this part,
but I think it's an accurate rendition.
I think to use
C++ templates, the
ring_view is a template,
I think you should have
general familiarity
with C++ and templates at this point.
At the time he wrote this,
I think it was a very
arcane and unusual skill,
but I think today, I think
most programmers should be
using the standard template library.
It's all templates, so they should have
some familiarity with templates.
They should have familiarity
with template parameters
and kind of a general idea what happens
when you put the wrong kind
of parameter into something.
So I would say I disagree
with you on that one.
I think that this is appropriate,
and I don't know if it
belongs in this document,
but I think that it's true as
it applies to this document.
- [Audience Member] I guess I
was just asking, in general,
is it nice to say that the audience is,
I guess that was my general question,
do you think it helps the document
that this is intended to?
And you think in the future,
anybody that wants to
write content should say,
"This is the audience I have in mind"?
I have to say, I hadn't
thought about that,
to tell you the truth.
I do believe that it's extremely useful
when you write any document
that you have a clear idea
who the audience is, that's no question.
Whether you have to
explicitly state it or not,
I think when we're talking
about something like this,
I think there's, people pretty
much gonna generally agree,
it wouldn't hurt to put it in.
Maybe it's just a little
verbosity you don't need.
But I think the more interesting
point that you raise is
that you have to, at least
when we're doing this,
and I'm talking about
a ring_view data type
that takes an input
iterator, or excuse me,
a forward iterator as a parameter,
I think that very clearly
defines the audience,
that audience requirements, and
I can write to that audience
so I feel okay about that.
But if I were gonna write a user manual,
I would have a whole different
way of looking at it.
So I don't know if that,
your question really wasn't a question,
and my answer really wasn't an answer,
but anyway, we had an
interesting exchange.
Okay.
There's a forward iterator.
Here's a class that has
two template parameters.
I picked this one because
it's actually very similar
to the ring_view, it has a
similar set of functions.
I'm not gonna go into this
because I don't want to run out of time,
it's pretty straightforward.
Here's a function,
documentation of function.
I'm hardly gonna touch that because
I don't think there's much
disagreement about that.
Yeah?
- [Audience Member] I think it
would be much better if you,
and just of the SGI documentation,
you reference that comment,
it also has documentation
on the library.
It does, and I have actually
have a pointer to that.
I'm not gonna do that because
it takes more time, but I
find the old SGI documentation
a better explanation of how to do it.
I think that the CPP
reference is very, very good,
it's quite good, and
I use it all the time,
but for purposes of the exposition today,
and I also believe if one wants to model
to help you, this C++
reference documentation
is not quite so regular as the old SGI.
And the SGI one, if you want to
I think learn how they did it,
I think it's a good place to start.
You'll be looking at CPP reference anyway,
but I think that the fact
that something is old,
it doesn't really, in this
case, diminish its value.
And there's other situations for that,
I'm 69 years old, so you're
not gonna convince me of that.
So,
I'm not convinced on that.
So let's suppose, so now
applying these principles,
I've got my, let's start building
the documentation for my ring_view.
Well, I got my name
there, in the copyright,
introduction, here's the sentence,
the first sentence we wrote down.
A ring_view is an adapter
which provides functions
for the first in/first out
access to existing storage.
It doesn't need anything else.
As a matter of fact, as
you guys reminded me,
could actually use a little less.
I'm sort of tempted to add
something, but every time I do,
I'm thinking it's not
really adding anything.
Then I have a tutorial
which basically is just my example,
and there's one sentence there that says
produces the following output,
and there's the output.
This is, okay, granted,
this is a little simpler
than what you're gonna run into,
but it illustrates the value
of restraining yourself
from adding something just because
you think you're expected to.
In this particular case, I
don't think adding anything
would add anything.
So we've already making
a lot of progress here.
We're almost to lunch now.
Notes, here I put the stuff
that didn't fit anywhere else,
like it's similar to std::queue
and how it differs from that, yada yada.
Maybe I probably put in more
than I should have here.
So, and if you've worked
with queue before,
basically tells you the API
is gonna be very similar.
If you think this way, and a lot of us do,
you're gonna say, oh it's
just like the std::queue,
except instead of allocating elements,
we have all the elements pre-allocated,
so we don't need an allocator,
we can have it on the stack.
Oh, voila, it's gonna be the same thing,
it's just gonna be a lot
faster with no overhead.
Or I can use it in a embedded system.
That kind of information might
be useful to put in there.
Some embedded systems
don't like using the heap.
And on the other hand,
if I've got a queue,
I would be done, I'd be fine here.
And so, or if I had a program
that used the std::queue
and then we want to port
it to an embedded system,
oh, well maybe just with,
we can see right here,
with just a little tweak,
we could get the effect
without having to turn our
whole system inside out.
Here I, and I put in the rationale,
the comments that I had
about the fact that this is
part of the proposal for, or similar to,
similar to a proposal for
the Standards Committee.
So there's not much to that.
And here I have the type.
This is pretty much taken from
the SGI example for a type.
It starts out with the function
signature and describes the
type requirements or the concepts
that that type requirement
is required to support,
the forward iterator.
And that matches up with this
construct inside our code,
which is from the tick library which says
is forward iterator,
and which will fail to
compile if I, in fact,
does not meet that requirement.
It's kind of a subtle point,
but it saves gobs of time.
We don't even have to add
anything to our documentation here
because since the forward
iterator is already defined
by the standard, we can just
suck it into our own document,
somebody can look it up
somewhere else, we're done,
that's that much we don't have to write.
We don't have to describe what I is
because it's done for,
we leveraged on the free stuff.
So also, I wrote
the ring_view, I was careful
not to define a destructor,
or a, my own version of copy or whatever,
because following the rule of zero,
if I don't define any of those,
they're automatically
defined by the compiler.
So more free stuff.
The minute I were to put
in my own destructor there,
whew, now I have to do more work.
More work doesn't pay.
And furthermore, I get
a significant part of
my documentation done because
once I know that the default
copy assignment and move
assignment and all of that stuff
is implemented automatically by C++,
that's another thing I
don't have to document.
All I have to do is refer to the concepts
as described in the standards manual.
So I cut my work a huge amount by,
now somebody's gonna come
along and make us think,
make my thing a little fancier
by putting in a destructor
which keeps count of something.
Oh, no, now he created
a lot more work for us,
and more opportunities for mistakes.
So I'm,
I have the documentation
when I'm writing the code.
So, and here's the SGI way
of describing something
with notation, associated types.
One type that we have in
here is the value type.
We just forward that to the
value type of the iterator.
I believe that other
cases, they have a type
for the value type.
I don't know, I did it this way.
Every iterator has a value type,
I said we're gonna use,
we're gonna make the value type
of this construct the same,
so we get that for free.
And then all we do then,
need to do is really
describe the operations
that are supported here,
and this is all they are.
Now, I had to horse around
with this a little bit
to get the R value thing
right and whatever.
And then I also included the link here
into my source code, let's
see if that still works.
No, that would be too much to expect.
So before I started mucking
with it, that worked.
But normally, it does work.
And then here's all the little extra stuff
that doesn't fit anywhere else.
For functions, I have no free
functions in this library
so there's nothing to
describe in references.
So that's the document for our ring_view.
It's been sort of mechanical
to actually produce it,
but let me tell you,
mechanical is much better
than getting stuck and having
to try to use your brain
on something that is really uninteresting.
So we can keep moving forward on this.
If we had more types, we'd
be doing the same thing,
be kind of straightforward.
The only thing that might be
different if we had more types
is our motivating example
might use a couple of types
to see how they would work in combination.
But other than that, it
wouldn't change all that much.
So I think, how much time have I got?
Eight minutes.
All right, were you falling asleep,
or you were just engrossed in it?
Okay.
So,
my documentation system also
produces the same thing in PDF
which is kind of nice, but it's just a,
strictly speaking, it's a freebie.
Okay, well I'm gonna recap here.
The purpose of the documentation
is to help the users.
The maintainers, they'll
use it a little bit,
but their real help is
inside the code itself.
The code in the documentation
should reflect the same intention.
And the, we're trying to
address the public API.
If you use a tool which generates
for every single function a
little bit of documentation,
I think it's a bad idea.
Most functions, they're
just listed in the type,
and most of them are fairly simple
that just the table is sufficient.
If a little bit's necessary,
or there's some extra
information, you can put it there.
I don't think you need
pages and pages with
one function header per page,
it just takes up a lot
of time, it's tedious,
and it doesn't add anything.
As a matter of fact, it detracts,
because it's intimidating,
it keeps people from wanting to,
finding what they need.
Okay, how do we build this?
We do it in conjunction with the code.
We do it to keep the
program from turning into
a miscellaneous bag of tricks.
And a miscellaneous bag
of tricks is something
that's hard to document,
and if, when we do that,
because people drive by and they
drop a suggestion into your box,
or somebody comes up with a great idea,
it sounds great and somebody codes it,
and then when you come back
and try and document it,
you find it wasn't a bad idea,
but it's now, it's too
late to take it out.
I know, this is better.
Somebody comes in with a little thing,
we update the document and whoa,
wait a minute, this doesn't
work, we see it right away.
So this this helps fight scope creep.
This helps keep you from
being everybody's slave.
It helps you to keep moving forward.
Well, everybody is actually
trying to quote help you out
by giving you more dumb ideas.
So if you have to do
this documentation part
and it's not that very hard
when you do it this way,
you'll find out right away
that something is a bad idea.
And you'll save tons of time
and not waste so much time on bad ideas.
So here's the general way it looks.
Introduction, examples, notes, rationale,
and then a reference,
reference is a separate part,
and that describes each
one of the following.
I don't have to beat this
to death, we already did.
What do we gain?
Agreement on its purpose.
Better conceptual integrity.
We have a type or a document
which describes something
that hangs together,
and not the miscellaneous bag of tricks.
It's just my slides that
make me repeat that,
but maybe it's a good thing to repeat.
Tools.
Tools don't create the document, we do.
Tools can help, or they can't,
they can make things worse.
I'm gonna pick out three tools
which I think are the most,
the only ones I'm familiar
with, let me put it that way.
Doxygen.
The good part is that it keeps
the reference documentation
in sync with the comments.
This is actually a huge feature,
it's very helpful, people
love it and for a good reason,
it is a good thing.
The bad thing is there's no obvious way
to create concept pages with it,
or actually other pages,
I've seen people do it with macros
or adding their own stuff or whatever.
It becomes so much work
to try and make Doxygen do
what I want it to do.
It just seemed more
work than it was worth.
The whole purpose for
me was to do less work,
and here I am, I felt I was arm wrestling
with Doxygen all the time.
Including narrative and
examples, a lot of work.
Generating alternative output formats
like PDF or eBook or whatever
also doesn't come free with it.
Yes, yes?
- [Audience Member] I think
one of the big examples
from Doxygen is with a little bit of work,
you can get a really nice choreograph
for your types and functions.
Well, maybe.
I don't think it's worth
the trade, but you know,
I'm not, I don't really want
to denigrate Doxygen so much
because it is actually a masterwork,
but I think what it really boils down to
is paraphrasing the header files,
and that's not really adding information,
that's not adding a part
that I'm missing here.
It kind of gives me what I'm not needing,
and not, it doesn't give
me what I am needing.
That's the way I see them.
DocBook.
DocBook is this XML thing.
It's a, BoostBook as a
variation of DocBook.
One great thing about DocBook is
that it decouples the
content from the formatting.
So you really describe all the,
all your code and whatever
using these XML tags,
and then you can generate the output
in the format that you want,
eBook, PDF, HTML, single page HTML.
I really love it, it's really great.
The bad part is editing
XML is like a huge, huge,
it's like torture, it's terrible.
So some people have come
up with ways to do it,
Emacs and whatever.
In Boost, Eric Niebler wrote
a component called QuickBook
which lets one take a
text file and prepare it
and it will generate the XML for you.
And that's been very popular within Boost.
I'm guessing half or more of
the documentation in Boost
uses that component.
My personal favorite
is XMLmind.
I've been trying to convince
people to use this in Boost.
I've had zero success.
Oh well.
But basically, it's an editor for XML.
And they have a free version,
and there's an add-on
which is for C++ documents
like the SGI, derived from the SGI scheme.
So basically it lets me
just create a type page,
create a concept page,
and then just fill it out,
almost as if it were a form.
And then from that, I can
generate PDF or whatever.
Setting up the tool
processing is kind of a pain,
but even so, that's the one that I love.
So I don't know if we have any time left,
since this session is over.
45 seconds, one question, one.
Who's, yes?
- [Audience Member] We always say that
for reference that at
some point your code,
with the documentation,
we have in the guidelines
to keep both in line.
Two, first is document less.
Don't document implementation features
in the user document, right?
And I've said that you should
have the implementation stuff
in the code, and if you do that,
then when you get a better implementation,
well, the interface is the same, right?
So there's less opportunity
for discrepancy.
So that's the overall piece
of view I have on that.
Document less, don't generate something
with everything in it,
generate something with just what the,
just describes the API,
and if you make changes in your code,
it won't matter unless
you really change the API.
That will help a lot.
- [Audience Member] I think
there's the difference
to make between documentation for actually
the users of the library,
and documentation for people
working on a library program.
Well, and I had made that distinction,
and I think I've made an effort
to make it a very obvious one
that this is for users of the library.
For people maintaining a library,
they just look at the code.
That's my, they don't need documentation.
Documentation is not,
this documentation I'm talking about here
is not that helpful to them.
- [Audience Member] Can I ask
a question to the audience
before everybody leaves?
How many of us here share the view that
user doc can be used as an effective tool
for quality control for
ensuring test coverage
for the code that we write?
Maybe just a show of hands?
Very few.
(audience member laughing)
All right, thanks.
Interesting question.
I hadn't thought of it myself.
- [Audience Member] It's
been very useful for myself,
but I wonder how to get this
idea across to my team members,
to my company, and maybe
we can have a talk.
Well, I think in, in the
context of what I said,
I think that it's kind of interesting
where that would fit in.
Yes.
It didn't occur to me.
- [Audience Member] Yes.
Thank you for showing up.
(audience applauding)
