1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
|
%
% PSI Programmer's Manual
%
% Section on coding style
%
% Created by
% David Sherrill, 1 February 1996
%
% Revised 27 June 1996 to discuss print levels
%
% Revised 12 June 2000 to reflect Edward Valeev's
% ideas on style
%
In the context of programming, {\em style} can refer to many
things. Foremost, it refers to the format of the source code: how to
use indentation, when to add comments, how to name variables, etc. It
can also refer to many other issues, such code organization,
modularity, and efficiency. Of course, stylistic concerns are often
matters of individual taste, but often validity and portability of the
code will ultimately depend on stylistic decisions made in the process
of code development. Hence some stylistic choices are viewed as
universally bad (e.g.\ not prototyping every function just because
``the code compiles and runs fine as is'', etc.). Admittedly, it is
easy to not have any style, but it takes years to learn what makes a
good one. A good programming style can reduce debugging and
maintenance times dramatically. For a large package such as
\PSIthree, it is very important to adopt a style which makes the code
easy to understand and modify by others. This section will give a few
brief pointers on what we consider to be a good style in programming.
\subsection{On the Process of Writing Software}
At first, we feel appropriate to touch upon the issue of programming
style as referred to the approach to writing software. Often,
``programming'' is used to mean ``the process of writing
software''. In general one has to distinguish ``writing software''
from ``programming'' meaning ``implementation'', because the latter is
only a part of the former and does not include documentation, etc. In
general, ``writing software'' should consist of five parts:
\begin{enumerate}
\item Get a clear and detailed understanding of what the code has to do (idea);
\item Identify key concepts and layout code and data organization (design);
\item Write source code (implementation);
\item Test the program and eliminate errors and/or design flaws (testing);
\item Write documentation (documentation).
\end{enumerate}
Thus, writing software is significantly more complex than just
coding. Each stage of writing software is as important as others and
should not be considered a waste of time. The code written without a
detailed understanding of what it has to do may not work
properly. Poorly designed code may not be flexible enough to
accomodate some new feature and will be rewritten. Poorly implemented
code may be too slow to be useful. A paper full of incorrect values
produced by your code may get you fired and will destroy your
reputation. A documentation-free code will most likely be useless for
others.
Of course, for very simple programs design and implementation may be
combined and documentation may consist of one line. However, for more
complex programs it is recommended that the five stages are
followed. This means that you should spend only about 20-40\% of your
time writing source code! Our experience shows that following this
scheme results in the most efficient approach to programming in the
long run.
To learn more on each stage of the software writing process, you may
want to refer to Stroustrup's ``C++ Programming Language'' book (3rd
Ed.) as the most common reference source not dedicated solely to one
narrow subject. Besides being an excellent description of C++, it is
also an introduction to writing software as well. Particular attention
is paid to the issue of {\em program design}.
\subsection{Design Issues}
Although C lacks the most powerful features of C++ as far as concepts
and data organization is concerned, Stroustrup says: ``Remember that
much programming can be simply and clearly done using only primitive,
data structures, plain functions, and a few library classes.'' This
means that one can write many useful and {\em well-written} programs
in C. Here are a few pointers that will assist you in structuring
your C program:
\begin{itemize}
\item Identify groups of variables having common function (e.g. basis
set, etc.) and organize them into structures. Use several levels of
hierarchy if necessary (e.g. a basis set is a collection of basis
functions each of which may be described by a structure). This is
called ``hierarchical ordering''.
\item Think as generally as possible. What you may not need today will
be asked for tomorrow. Design data structures that are flexible and
modular, i.e. one can be easily modified without affecting the others
(e.g. you do not want the structure describing basis sets to know
anything about the type of basis functions it contains so that plane
waves can be used as easily as Gaussians).
\item Write ``constructors'' for the structures, i.e. functions which
will initialize data in the structures (e.g. read basis set
information). Make as many ``constructors'' as necessary (e.g. basis
set info can be read from the checkpoint file or from \pbasisdat). If
it is difficult or impossible to write a ``constructor'' for some data
structure is a sign that your data hierachy is poorly designed and
there are mutual dependencies. Spend more time designing the
system. If it doesn't help, then use source code comments heavily to
describe the relationships not reflected in the code itself.
\item Use global variables sparringly. Placing a variable into global
scope leaves it unprotected against ``unauthorized'' use or
modification (we are not talking about security here; it is a good
idea to protect data from the programmer, because if you do not want
some data \celem{A} to be modified by function \celem{B}, do not make
\celem{A} available to \celem{B}) and may also have impact on
program's performance. Sometimes it is a good idea to use global data
to reduce the cost of passing that data to a function. However, the
same effect may be achieved by organizing that data into a local
structure and passing the structure instead.
\item Learn how to use \celem{static} variables local to a source
file, it is a very powerful tool to protect data in a C program.
\item Organize the source code such as to emphasize further the
structure of the program (see section \ref{sourcecode}).
\end{itemize}
More material on data organization may be found in the Stroustrup's
book.
\subsection{Organization of Source Code} \label{sourcecode}
It is almost universally agreed that breaking the program up into
several files is good style. An 11,592 line Fortran program, for
example, is very inconvenient to work with, for several reasons:
first, it can be difficult to locate a particular
function\footnote{Following the convention of C, the words function
and subroutine will be used interchangeably.} or statement; second,
every recompilation during debugging involves compiling the {\em
entire} file. Having several small files generally makes it easier to
find a particular piece of code, and only source files which have been
modified need to be recompiled, greatly enhancing the efficiency of
the programmer during the debugging process. For smaller programs, it
is recommended that the programmer have one file for each subroutine,
giving each file the name of the subroutine (abbreviated filenames may
be specified if the function names are too long). For larger
programs, it may be helpful to group similar functions together into a
single file.
In C programs, we also consider it a good idea to place all the
\celem{\#include} statements in a file such as \file{includes.h},
which is subsequently included in each relevant C source file. This
is helpful because if a new header file needs to be added, it can
simply be added to \file{includes.h}. Furthermore, if a source file
suddenly needs to have access to a global variable or function
prototype which is already present in one of the header files, then no
changes need to be made; the header file is {\em already} included. A
downside to this approach is that each header file is included in
every source file which includes \file{includes.h}, regardless of
whether a particular header file is actually needed by that source
file; this could potentially lead to longer compile times, but it
isn't likely to make a discernable difference, at least in
C.\footnote{C++, which includes much of the actual code in header
files, is a different matter.}
Along similar lines, it is helpful to {\em define} all global
variables in one location (in the main program file, or else within
\file{globals.c}), and they should be {\em declared} within another
standard location (perhaps \file{globals.h}, or
\file{common.h}).\footnote{See page 33 of Kernighan and Ritchie, 2nd
Ed., for an explanation of {\em definition vs.~declaration}.}
Similarly, if functions are used in several different source code
files, the programmer may wish to place all function prototype
declarations in a single header file, with the same name as the
program or library, or perhaps called \file{protos.h}.
\subsection{Formatting the Code}
By formatting, we mean how many spaces to indent, when to indent, how
to match up braces, when to use capital vs.~lower case letters, and so
forth. This is perhaps a more subjective matter than those previously
discussed. However, it is certainly true that some formatting styles
are easier to read than others. For already existing code, we
recommend that you conform to the formatting convention already
present in the code. The author of the code is likely to get upset
when he sees that you're incorporated code fragments with a formatting
style which differs from his! On the other hand, in certain rare
cases, it might be more beneficial to incorporate a different style:
in the conversion of \module{intder95} from old-style to new-style
input, we used lower-case lettering instead of the all-caps style of
the original program. This was very useful in helping us locate which
changes we had made.
It is very common that statements within loops are indented. Loops
within loops are indented yet again, and so on. This practice is
near-universal and very helpful. Computational chemistry programs
often require many nested loops. The consequence of this is that
lines can be quite long, due to all those spaces before each line in
the innermost loops. If the lines become longer than 80 characters,
they are hard to read within a single window; please try to keep your
lines to 80 characters or less. This means that you should use about
2-4 spaces per indentation level.
The matching of braces, and so forth, is more variable, and we
recommend you follow the convention of {\em The C Programming
Language}, by Kernighan and Ritchie, or perhaps the style found in
other \PSIthree modules.
\subsection{Naming of Variables}
All non-trivial data must be given descriptive names, although
extremely long names are discouraged. For example, compound variable
names like \celem{num\_atoms} or \celem{atom\_orbit\_degen} should be
preferred to \celem{nat} or \celem{atord}, so that non-specialists
could understand the code. It is also a good idea to put a
descriptive comment where a non-trivial variable is declared. However,
simple loop indices should generally be named \celem{i,j,k} or
\celem{p,q,r}.
\PSIthree\ programs have certain conventions in place for names of
most common variables, as shown in the Table \ref{tbl:VarNaming}.
\begin{table}
\caption{Some Variable Naming Conventions in \PSIthree}
\label{tbl:VarNaming}
\begin{center}
\begin{tabular}{ll}
\hline \hline
\multicolumn{1}{c}{Quantity} &
\multicolumn{1}{c}{Variable(s)} \\ \hline
Number of atoms & na, natom, num\_atoms \\
Number of atoms * 3 & natom3, num\_atoms3 \\
Nuclear repulsion energy & enuc, repnuc \\
SCF energy & escf \\
Number of atomic orbitals & nbfao, num\_ao, nao \\
Number of symmetry orbitals & nbfso, num\_so, nso \\
Size of lower triangle \\
\hspace{0.5cm} of AO's, SO's & nbatri, nbstri; ntri \\
Input file pointer & infile \\
Output file pointer & outfile \\
Offset array & ioff \\
Number of irreps & num\_ir, nirreps \\
Open-shell flag & iopen \\
Number of orbitals per irrep & orbs\_per\_irrep, orbspi, mopi \\
Number of closed-shells \\
\hspace{0.5cm} per irrep & docc, clsd\_per\_irrep, clsdpi \\
Number of open-shells \\
\hspace{0.5cm} per irrep & socc, open\_per\_irrep, openpi \\
Orbital symmetry array & orbsym \\
\hline \hline
\end{tabular}
\end{center}
\end{table}
\subsection{Printing Conventions}
At the moment, there isn't really a standard method for a PSI program
to determine how much information to print to \file{output.dat}. Some
older \PSIthree\ modules read a flag usually called \keyword{IPRINT}
which is a decimal representation of a binary number. Each bit is a
printing option (yes or no) for the different intermediates particular
to the program.
A practice which is probably preferable is to have a different print
flag (boolean) for each of the major intermediates used by a program,
and to have an overall print option (decimal) whose value determines
the printing verbosity for the quantities without a specific printing
option. The overall print option should be specified by a keyword
\keyword{PRINT\_LVL}, and its action should be as in Table
\ref{tbl:iprint}.
\begin{table}
\caption{Proposed Conventions for Printing Level}
\label{tbl:iprint}
\begin{center}
\begin{tabular}{ll}
\hline \hline
0 & Almost no printing; to be used by driver programs \\
& with -quiet option \\
1 & Usual printing (default) \\
2 & Verbose printing \\
3 & Some debugging information \\
4 & Substantial debugging information \\
5 & Print almost all intermediates unless arrays too large \\
6 & Print everything \\
\hline \hline
\end{tabular}
\end{center}
\end{table}
\subsection{Commenting Source Code}
\label{code-commenting}
It is absolutely mandatory that each source file contains a reasonable
number of comments. When a significant variable, data type, or
function is declared, it must be accompanied with some descriptive
information written in English. Every function prototype or body of
it has to be preceeded by a short description of its purpose,
algorithm (desirable; if it is too complex, provide a reference), what
arguments it takes and what it returns.
Having said this, we will argue against excessive commenting: don't
add a comment every time you do \celem{i++}! It will actually make
your code harder to read. Be sensible.
As of spring 2002, we have adopted the {\tt doxygen} program to
automatically generate source code documentation. This program scans
the source code and looks for special codes which tell it to add the
given comment block to the documentation list. The program is very
fancy and can generate documentation in man, html, latex, and rtf
formats. The file \file{psi3.dox} is the {\tt doxygen} configuration
file. The source code should be commented in the following way to
work with {\tt doxygen}.
The first file of each library defines a ``module'' via a special
comment line:
\begin{verbatim}
/*! \defgroup PSIO libpsio: The PSI I/O Library */
\end{verbatim}
Note the exclamation mark above --- it is required by {\tt doxygen}.
The line above defines the {\tt PSIO} key and associates it with the
title ``The PSI I/O Library.'' Each file belonging to this group will
have a special comment of the following form:
\begin{verbatim}
/*!
** \file close.c
** \ingroup (PSIO)
*/
\end{verbatim}
This tells {\tt doxygen} that file \file{close.c} should be
documented, it should be added to the list of documented files, and it
belongs to the {\tt PSIO} group.
All functions should be commented as in the following:
\begin{verbatim}
/*!
** PSIO_CLOSE(): Closes a multivolume PSI direct access file.
**
** \param unit = The PSI unit number used to identify the file to all read
** and write functions.
** \param keep = Boolean to indicate if the file should be deleted (0) or
** retained (1).
**
** Returns: always returns 0
**
** \ingroup (PSIO)
*/
int psio_close(ULI unit, int keep)
...
\end{verbatim}
This will add the function {\tt psio\_close} to the list, associate it with
the {\tt PSIO} module, and define the various arguments.
{\em Please note:} In addition to listing all the parameters and return
values, it is very valuable to explain what the function actually does.
Add this explanation immediately after the function name (see above). This
explanation might be a few words, or an entire paragraph, as necessary.
|