Chapter 7. Documentation

Documentation is like sex: when it's good, it's very, very good; and when it's bad, it's still better than nothing.

Dick Brandon

Documentation: for most development programmers it's a millstone, but for maintenance programmers it's a life-line. More importantly, very few programmers are exclusively in one role or the other. Most developers write code that they then have to maintain themselves. Or else they have to maintain other people's code in order to develop their own.

The problem is that any code of your own that you haven't looked at for six or more months might as well have been written by someone else [33]. The young, smart, optimistic you—who's creating the code—will undoubtedly find it tedious to document your understanding of what that code does and how it does it. But the older, wiser, sadder you—who later has to fix, extend, and adapt that code—will treasure the long-forgotten insights that your documentation preserves.

In that sense, documentation is a love letter that you write to your future self.

Types of Documentation

Distinguish user documentation from technical documentation.

End users will rarely read your code, or your comments. If they read anything at all, they'll run your module or application through perldoc[34] and read whatever emerges.

On the other hand, maintainers and other developers may also read your POD[35], but they'll spend far more of their time looking directly at your code.

So it makes sense to put user documentation in the "public" sections of your code's POD (i.e., in the =head1, =head2, and =over/=item/=back sections), and relegate the technical documentation to "non-public" places (i.e., to the =for and =begin/=end POD sections and to comments).

More importantly, distinguish between the content of user and technical documentation. In particular, don't put implementation details in user documentation. It wastes your time and it annoys the user. Tell the user what the code does, not how the code does it, unless those details are somehow relevant to the users' use of that code.

For example, when documenting a set of list operations for users, tell them that pick( ) takes a list and selects one element at random, that shuffle( ) takes a list and returns a randomly reordered version of that list, and that zip( ) takes two or more array references and produces a single list that interleaves the array values. You may choose to mention that pick( ) and shuffle( ) do their jobs in a genuinely random and unbiased manner, but there's no need to explain how that miracle is achieved.

On the other hand, your module may also provide a set of specialist sorting routines: sort_radix( ), sort_shell( ), sort_pigeonhole( ). When documenting these, you will obviously need to at least mention the different algorithms they employ, and the conditions under which each might be a superior choice.

Boilerplates

Create standard POD templates for modules and applications.

One of the main reasons documentation can often seem so unpleasant is the "blank page effect". Many programmers simply don't know how to start, or what to say.

One of the best ways to make writing documentation less forbidding (and hence more likely to actually occur) is to circumvent that initial empty screen by providing a template that developers can cut and paste into their code.

For a module, that documentation template might look something like Example 7-1. For an application, the variation shown in Example 7-2 is more appropriate. Of course, the specific details that your templates provide may vary from those shown here, according to your other coding practices. The most likely variation will be in the licence and copyright, but you may also have specific in-house conventions regarding version numbering (see Chapter 17), or the grammar of diagnostic messages (see Chapter 13), or the attribution of authorship.

Example 7-1. User documentation template for modules

=head1 NAME

<Module::Name> - <One-line description of module's purpose>


=head1 VERSION

The initial template usually just has:

This documentation refers to <Module::Name> version 0.0.1.


=head1 SYNOPSIS

    use <Module::Name>;
    # Brief but working code example(s) here showing the most common usage(s)

    # This section will be as far as many users bother reading,

    # so make it as educational and exemplary as possible.


=head1 DESCRIPTION

A full description of the module and its features.
May include numerous subsections (i.e., =head2, =head3, etc.).


=head1 SUBROUTINES/METHODS

A separate section listing the public components of the module's interface.
These normally consist of either subroutines that may be exported, or methods
that may be called on objects belonging to the classes that the module provides.
Name the section accordingly.

In an object-oriented module, this section should begin with a sentence of the
form "An object of this class represents...", to give the reader a high-level
context to help them understand the methods that are subsequently described.


=head1 DIAGNOSTICS

A list of every error and warning message that the module can generate
(even the ones that will "never happen"), with a full explanation of each
problem, one or more likely causes, and any suggested remedies.
(See also "Documenting Errors" in Chapter 13.)

=head1 CONFIGURATION AND ENVIRONMENT

A full explanation of any configuration system(s) used by the module,
including the names and locations of any configuration files, and the
meaning of any environment variables or properties that can be set. These
descriptions must also include details of any configuration language used.
(See also "Configuration Files" in Chapter 19.)


=head1 DEPENDENCIES

A list of all the other modules that this module relies upon, including any
restrictions on versions, and an indication of whether these required modules are
part of the standard Perl distribution, part of the module's distribution,
or must be installed separately.


=head1 INCOMPATIBILITIES
A list of any modules that this module cannot be used in conjunction with.
This may be due to name conflicts in the interface, or competition for
system or program resources, or due to internal limitations of Perl
(for example, many modules that use source code filters are mutually
incompatible).

=head1 BUGS AND LIMITATIONS
A list of known problems with the module, together with some indication of
whether they are likely to be fixed in an upcoming release.
Also a list of restrictions on the features the module does provide:
data types that cannot be handled, performance issues and the circumstances
in which they may arise, practical limitations on the size of data sets,
special cases that are not (yet) handled, etc.

The initial template usually just has:

There are no known bugs in this module.
Please report problems to <Maintainer name(s)>  (<contact address>)
Patches are welcome.

=head1 AUTHOR

<Author name(s)>  (<contact address>)

=head1 LICENCE AND COPYRIGHT

Copyright (c) <year> <copyright holder> (<contact address>). All rights reserved.

followed by whatever licence you wish to release it under.
For Perl code that is often just:

This module is free software; you can redistribute it and/or
modify it under the same terms as Perl itself. See L<perlartistic>.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Example 7-2. User documentation template for applications

=head1 NAME

<application name> - <One-line description of application's purpose>


=head1 VERSION

The initial template usually just has:

This documentation refers to <application name> version 0.0.1.


=head1 USAGE

    # Brief working invocation example(s) here showing the most common usage(s)

    # This section will be as far as many users ever read,
    # so make it as educational and exemplary as possible.


=head1 REQUIRED ARGUMENTS

A complete list of every argument that must appear on the command line.
when the application  is invoked, explaining what each of them does, any
restrictions on where each one may appear (i.e., flags that must appear
before or after filenames), and how the various arguments and options
may interact (e.g., mutual exclusions, required combinations, etc.)


If all of the application's arguments are optional, this section
may be omitted entirely.


=head1 OPTIONS

A complete list of every available option with which the application
can be invoked, explaining what each does, and listing any restrictions,
or interactions.


If the application has no options, this section may be omitted entirely.


=head1 DESCRIPTION

A full description of the application and its features.
May include numerous subsections (i.e., =head2, =head3, etc.).


=head1 DIAGNOSTICS

A list of every error and warning message that the application can generate
(even the ones that will "never happen"), with a full explanation of each
problem, one or more likely causes, and any suggested remedies. If the
application generates exit status codes (e.g., under Unix), then list the exit
status associated with each error.


=head1 CONFIGURATION AND ENVIRONMENT

A full explanation of any configuration system(s) used by the application,
including the names and locations of any configuration files, and the
meaning of any environment variables or properties that can be set. These
descriptions must also include details of any configuration language used.
(See also "Configuration Files" in Chapter 19.)


=head1 DEPENDENCIES
=head1 INCOMPATIBILITIES
=head1 BUGS AND LIMITATIONS
=head1 AUTHOR
=head1 LICENCE AND COPYRIGHTThese sections are the same as in Example 7-1.

You could make it easy to load these files from templates by configuring your text editor appropriately. In your vim configuration file:

iab papp  ^[:r ~/.code_templates/perl_application.pl^M

Or in your Emacs configuration:

;; Load an application template in a new unattached buffer...
(defun application-template-pl ( )
  "Inserts the standard Perl application template"  ; For help and info.


  (interactive "*")                                 ; Make this user accessible.

  (switch-to-buffer "application-template-pl")
  (insert-file "~/.code_templates/perl_application.pl"))
;; Set to a specific key combination...(global-set-key "C-ca" 'application-template-pl)

Extended Boilerplates

Extend and customize your standard POD templates.

The two templates recommended in the previous section represent only the minimum amount of information that should be provided to the user. There are many more possibilities that your team might choose to add to its standard template, such as:

=head1 EXAMPLES

Many people learn better by example than by explanation, and most learn better by a combination of the two. Providing a /demo directory stocked with well-commented examples is an excellent idea, but your users might not have access to the original distribution, and the demos are unlikely to have been installed for them. Adding a few illustrative examples in the documentation itself can greatly increase the "learnability" of your code.

=head1 FREQUENTLY ASKED QUESTIONS

Incorporating a list of correct answers to common questions may seem like extra work (especially when it comes to maintaining that list), but in many cases it actually saves time. Frequently asked questions are frequently emailed questions, and you already have too much email to deal with. If you find yourself repeatedly answering the same question by email, in a newsgroup, on a web site, or in person, answer that question in your documentation as well. Not only is this likely to reduce the number of queries on that topic you subsequently receive, it also means that anyone who does ask you directly can simply be directed to read the fine manual.

=head1 COMMON USAGE MISTAKES

This section is really "Frequently Unasked Questions". With just about any kind of software, people inevitably misunderstand the same concepts and misuse the same components. By drawing attention to these common errors, explaining the misconceptions involved, and pointing out the correct alternatives, you can once again pre-empt a large amount of unproductive correspondence. Perl itself provides documentation of this kind, in the form of the perltrap manpage.

=head1 SEE ALSO

Often there will be other modules and applications that are possible alternatives to using your software. Or other documentation that would be of use to the users of your software. Or a journal article or book that explains the ideas on which the software is based. Listing those in a "See Also" section allows people to understand your software better and to find the best solution for their problem themselves, without asking you directly[36].

=head1 (DISCLAIMER OF) WARRANTY

This subsection is essential in any software that is likely to be used outside your own organization. It should be in a section separate from the "Copyright and Licence", and it should be drafted by a competent legal professional (so you have someone else to sue if someone sues you). If you're not part of a corporation and don't have your own attack lawyers, a useful starting point might be clauses 11 and 12 of the GNU Public License (http://www.gnu.org/copyleft/gpl.html)[37].

=head1 ACKNOWLEDGEMENTS

Acknowledging any help you received in developing and improving your software is plain good manners. But expressing your appreciation isn't only courteous; it's also enlightened self-interest. Inevitably people will send you bug reports for your software. But what you'd much prefer them to send you are bug reports accompanied by working bug fixes. Publicly thanking those who have already done that in the past is a great way to remind people that patches are always welcome.

Location

Put user documentation in source files.

Having decided what to provide as user documentation, the next question is where to provide it. The answer is: put the documentation in the same file as the module or application itself (i.e., in the relevant .pm or .pl file).

The other common alternative is to put the documentation in its own separate .pod file. This is possible because perldoc is smart enough to look for POD files as well as source files when searching for documentation. The problem is that this approach works only if the appropriate .pod document has been installed along with the module or application, and has been installed somewhere in perldoc's search path, which is unlikely.

In contrast, if the user documentation is placed directly in the appropriate .pm or .pl file, it will automatically be available anywhere the module or application itself is.

Contiguity

Keep all user documentation in a single place within your source file.

Even though Perl allows you to interleave POD sections between chunks of source code, don't.

User documentation that is fragmented into numerous small pieces distributed throughout the code is much harder to maintain in a consistent state, because you have to sift through the intervening code fragments to find it or compare it.

It is sometimes argued that having documentation near the code that it documents can help maintain consistency between the two. In practice, the opposite often seems to be the case: the necessity to go elsewhere in a file in order to update documentation after a code change actually seems to make it more likely that developers will do so. When the documentation is right on hand it's somehow easier to overlook or ignore. Of course, that's not going to be the case for everyone. Many people do find documenting a subroutine easier when the documentation is immediately to hand.

A more important reason not to intersperse code and documentation is that doing so usually produces either contorted code or confused documentation. Keeping documentation near the code it explains will frequently force you to lay the code out in an unnatural order, so as to ensure sensible exposition in the documentation. Or else it will force you to present your documentation in an unnatural order, so as to ensure a sensible layout of the code. Neither of these outcomes is desirable, and both can be avoided by keeping the documentation in its own separate, coherent section of the source file.

Position

Place POD as close as possible to the end of the file.

Having decided to keep the documentation together, the obvious question is whether to place it at the start or the end of the file.

There seems to be no particular reason to place it at the beginning. Anyone who is looking at the source is presumably most interested in the code itself, and will appreciate seeing it immediately when they open the file, rather than having to wade though several hundred lines of user documentation first. Moreover, the compiler is able to do a slightly more efficient job it if doesn't have to skip POD sections before it finds any code to compile.

So place your POD at the end of the file, preferably after the _ _END_ _ marker so that the compiler doesn't have to look at it at all. Or, if you're using a _ _DATA_ _ section in your implementation, wrap the documentation in =pod/=cut directives and place it just before the _ _DATA_ _ marker.

Technical Documentation

Subdivide your technical documentation appropriately.

When it comes to technical documentation, use separate .pod or plain-text files for your external documentation, design documents, data dictionaries, algorithm overviews, change log, and so on. Make sure that the "See Also" section of your user documentation refers to these extra files.

Use comments (and "invisible" POD directives) for internal documentation, explanations of implementation, maintenance notes, et cetera. The following guidelines give details on each of these points.

Comments

Use block templates for major comments.

Create comment templates that are suitable for your team. For example, to internally document a subroutine or method, you might use something like:

############################################
# Usage      : ????
# Purpose    : ????
# Returns    : ????
# Parameters : ????
# Throws     : no exceptions
# Comments   : none# See Also   : n/a

which might be filled in like so:

############################################
# Usage      : Config::Auto->get_defaults( )
# Purpose    : Defaults for 'new'
# Returns    : A hash of defaults
# Parameters : none
# Throws     : no exceptions
# Comments   : No corresponding attribute,
#            : gathers data from each
#            : attr_def attribute# See Also   : $self->set_default( )

Structured comments like that are usually better than free-form comments:

# This method returns a hash containing the defaults currently being
# used to initialize configuration objects. It takes no arguments.
# There isn't a corresponding class attribute; instead it collects
# the necessary information from the various attr_def attributes. There's# also a set_default( ) method.

Templates produce commenting that is more consistent and easier to read. They're also much more coder-friendly because they allow developers to simply "fill in a form". Comment templates also make it more feasible to ensure that all essential information is provided, and to identify missing information easily, by searching for any field that still has a ???? in its "slot".

Your team might prefer to use some other template for structured comments—maybe even just this:

### CLASS METHOD/INSTANCE METHOD/INTERFACE SUB/INTERNAL UTILITY ###

# Purpose:  ????# Returns:  ????

In this version, the type of subroutine can be specified by retaining one of the four titles, and only the essential information is recorded:

### CLASS METHOD ###
# Purpose:  Defaults for 'new'# Returns:  A hash of defaults

Note that it's particularly useful to indicate how the subroutine is expected to be used—either with a Usage: field, or with a title like CLASS METHOD. In Perl, the sub keyword is used to declare normal subroutines, class methods, instance methods, internal-use-only utilities, as well as the implementation of overloaded operators. Knowing which role (or roles) a particular subroutine is supposed to play makes it much easier to understand the subroutine, to use it correctly, and to maintain it.

A templated block comment like those recommended earlier should be used to document each component of a module or application. "Components" in this context means subroutines, methods, packages, and the main code of an application.

Algorithmic Documentation

Use full-line comments to explain the algorithm.

Chapter 2 recommends coding in paragraphs. Part of that advice is to prefix each paragraph with a single-line comment.

That comment should explain at a high level what the associated paragraph contributes to the overall process implemented by the code. Ideally, if all the paragraph comments were to be extracted, they should summarize the algorithm by which the code performs its task.

Keep each such comment strictly to a single line. Any more than that interrupts the code excessively, making it harder to follow. If the paragraph is doing something too complicated to be explained in a single line, that is a sign that the code either needs to be split into several paragraphs, or else refactored out into a subroutine (which can then be given a more expansive block comment).

For example:

sub addarray_internal {
    my ($var_name, $needs_quotemeta) = @_;

    # Record original...
    $raw .= $var_name;

    # Build meta-quoting code, if required...
    my $quotemeta = $needs_quotemeta ? 'map {quotemeta $_}'
                                     : $EMPTY_STR
                                     ;

    # Expand elements of variable, conjoin with ORs...
    my $perl5pat
        = qq{(??{join q{|}, $quotemeta @{$var_name}})};

    # Insert debugging code if requested...
    my $type = length $quotemeta ? 'literal' : 'pattern';
    debug_now("Adding $var_name (as $type)");
    add_debug_mesg("Trying $var_name (as $type)");

    # Add back-translation...
    push @perl5pats, $perl5pat;

    return;}

Note, however, that the very first paragraph—which will always be unpacking the subroutine's parameters (see Chapter 9)—does not require a comment. Nor does the final return statement.

Elucidating Documentation

Use end-of-line comments to point out subtleties and oddities.

The guidelines in this book aim to help you write code that's self-documenting, so most lines within a single paragraph shouldn't require extra "hints" in order to understand them.

But self-documentation is always in the eye of the original author, and code that seemed perfectly clear when it was written may be somewhat less intelligible when it's re-read six months later.

Comprehensibility can suffer particularly badly when the code incorporates jargon from the problem domain. Terms that were extremely familiar to the original designers and implementers might mean nothing to those who later have to maintain the source. For example, you could inherit code like this:

my $QFETM_func_ref;

if ($QFETM_func_ref  = get_GET( )) {
    make_futtock($QFETM_func_ref);
}

$build_mode = oct $arg{mode};

in which case, the judicious application of trailing comments is appropriate:

my $QFETM_func_ref;  # stores Quantum Field Effect Transfer Mode function

# Build futtock representation if remote data is available...
if ($QFETM_func_ref  = get_GET( )) {    # instead of get_POST( )
    make_futtock($QFETM_func_ref);     # futtock: a rib of a ship's frame
}$build_mode = oct $arg{mode};   # *From* octal, not *to* octal

End-of-line comments should be kept pithy. If you feel that an elucidating comment needs more than the remainder of the current line, then use a discursive comment instead (see "Discursive Documentation" later in this chapter).

Defensive Documentation

Comment anything that has puzzled or tricked you.

The final line in the previous example demonstrates the use of an in-line comment to overcome a maintainer's personal stumbling block:

$build_mode = oct $arg{mode};   # *From* octal, not *to* octal

Many programmers mistakenly assume that the oct builtin returns the octal version of its argument, when it actually converts its argument from an octal representation to decimal. That comment may have been added when the code was originally written (presumably in a d'oh! moment after several hours of fruitless debugging), or it may have been appended by a subsequent maintainer (to immortalize their own Homeric realization). Either way, by commenting it explicitly, that same false expectation will thereafter be averted every time someone new reads the code.

An in-line comment is appropriate whenever you encounter a subtle bug, or whenever you write some subtle code. "Subtle" has a very precise definition here: it means that you either had to look something up in a manual, or had to spend more than five seconds thinking about it before you understood its syntax or semantics.

For example, this:

@options = map +{ $_ => 1 }, @flags;

needs to be commented:

@options = map +{ $_ => 1 }, @flags;    # Anon hash ctor, not map block!

In general, if it puzzled or tricked you once, it will puzzle or trick you—or whoever comes after you—again. To avoid that, leave a Hyre Be Dragones comment in the code.

Indicative Documentation

Consider whether it's better to rewrite than to comment.

More often than not, the need to leave hints in the code indicates that the code itself is in need of reworking. For example, if the final example of the previous section had used a map block (as suggested in the "Mapping and Grepping" guideline in Chapter 8), then it would look like this instead:

@options = map { {$_ => 1} } @flags;

in which case the trailing comment would probably not be necessary. The outer braces after the map would obviously be block delimiters, because under the Chapter 8 guideline every map is followed by a block. The inner braces might still be slightly disconcerting, but as the map block is expected to return a value, it would be easy enough to deduce that those inner brackets must be producing a value, and hence must be a hash constructor.

Of course, if that still weren't obvious enough, a trailing comment would be appropriate. But now it could be much more to the point:

@options = map { {$_ => 1} } @flags;   # map block returns hash ref

Discursive Documentation

Use "invisible" POD sections for longer technical discussions.

The =for and =begin/=end POD directives provide an easy way to create large blocks of text that are ignored by the compiler and don't produce any visible output when the surrounding file is processed by a POD formatter. So these directives provide an easy way to embed extended pieces of internal documentation within your source.

The =for directive is identical to a =begin/=end pair, except that it allows only a single paragraph of content, terminated by an empty line. This might well be construed as a feature, in that it encourages conciseness[38]. But note that you still have to provide a trailing =cut, to switch the compiler back from skipping documentation to compiling Perl code.

Both these forms of block commenting take a "format name" after the keyword. Normally this name would be used to indicate which formatting tool the documentation is intended for (e.g., =for html …, =for groff …, =for LaTeX …), but it is far more useful as a means to specify the kind of internal documentation you are writing. Then, provided the description you choose doesn't match the name of one of the standard POD formatters, the resulting POD block will be effectively invisible outside the source code. An easy way to ensure that invisibility is to capitalize the description and put a colon at the end of it.

For example, you can use this approach to record your rationale for unusual design or implementation decisions:

=for Rationale:
     We chose arrays over hashes here because profiling indicated over
     99% of accesses were iterated over the entire set, rather than being
     random. The dataset is expected to grow big enough that the better
     access performance and smaller memory footprint of a big array will
     outweigh the awkwardness of the occasional binary-chop search.=cut

You can make notes on possible improvements that you don't currently have time to design or implement:

=for Improvement:
     Would be handier if this subroutine also accepted UMT values=cut

Or explain any obscure pieces of domain-specific information that necessitated unusual implementation approaches:

=for Domain:
     No observation is ever recorded without an error bound. Hence the
     use of interval arithmetic in the next three subroutines.=cut

Or mark sections that might benefit from optimization or that ought to be rewritten, making note of the conditions under which that might be possible:

=for Optimization:
     This parser would almost certainly benefit from the use of
     progressive matching with m/G.../gcxms, rather than relying
     on successive prefix substitutions.
     Reconsider when everyone is using at least Perl 5.6.1.=cut

Or highlight workarounds necessitated by limitations in Perl itself (or perl itself):

=for Workaround:
     Have to use a localized package variable here, rather than a
     lexical. A closure would be better of course, but lexicals don't seem
     to propagate properly into regexes under 5.8.3 (or earlier). This
     problem has been reported via perlbug.=cut

Avoid the =begin/=end form unless the annotation is large and requires multiple paragraphs or embedded code examples:

=begin Optimization:

     This parser would almost certainly benefit from the use of
     progressive matching with m/G.../gcxms, as in:

        while (pos $text < length $text) {
            if (m/G ($TYPENAME)/gcxms) {
                push @tokens, Token::Type->new({ name => $1 });
            }
            elsif (m/G ($VARNAME)/gcxms) {
                push @tokens, Token::Var->new({ alias => $1 });
            }
            # etc.
            else {
                croak q{Don't understand '},
                      substr($text, pos $text, 20),
                      "'
";
            }
         }

     Reconsider when everyone is using at least Perl 5.6.1.

=end Optimization=cut

Note that, unlike the consolidated "visible" POD written for user documentation, the "invisible" POD containing these technical discussions should be kept as close as possible to the code it refers to. Furthermore, since this documentation is "for internal use only" and never intended for any POD formatter, don't use POD mark-up within these sections.

Proofreading

Check the spelling, syntax, and sanity of your documentation.

The point of all documentation is communication: either with the users of your code, or with those who maintain it. To be effective, documentation must communicate effectively. It must be without distractions (like spelling mistakes), it must be comprehensible (i.e., syntactically correct), it must be unambiguous, and it must make sense.

So, although it's important to write your documentation, it's far more important to read it after it's written, to make sure it will do the job you created it to do.

The best way to proofread a document is to look at a "rendered" version of it. That is, don't simply reread the POD source you just wrote. Instead, convert that POD to plain text (using perldoc) or to HTML (via pod2html) or even to LaTeX (with pod2latex), and then read through it using the appropriate display tool.

Better still, have someone who's unfamiliar with the code read through your documentation. A new reader will be far better able to recognize when some part of your explanation is confusing, ambiguous, or otherwise unenlightening.



[33] Eagleson's Law again.

[34] perldoc is a command-line utility that comes standard with Perl. It locates, extracts, and presents documentation from the Perl manpages, the standard library, and any other modules installed on your system. A good place to start is:

> perldoc perldoc

[35] POD is the "Plain Old Documentation" format, a simple mark-up language for embedded documentation that is recognized by the Perl compiler. If you're unfamiliar with it, take a look at the perlpod manpage.

[36] By now you have no doubt detected the ulterior motive for providing more extensive user manuals and written advice. User documentation is all about not having to actually talk to users.

[37] However, the author is not a competent legal professional and this suggestion is offered for information purposes only and in no way constitutes legal advice. This recommendation is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

[38] The only thing harder than finally convincing coders to write comments is then convincing them to write short comments.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset