Changes between Initial Version and Version 1 of Development/Code/CodingStyle


Ignore:
Timestamp:
Mar 20, 2010, 10:30:14 PM (16 years ago)
Author:
loomis
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Development/Code/CodingStyle

    v1 v1  
     1[[TracNav]]
     2
     3= Coding style on Quattor modules =
     4
     5== Introduction ==
     6
     7All major software projects have a set of guidelines for their code. It
     8helps people to understand each other's code, and even your own code a
     9few months (or days!) after you wrote it for the first time.
     10
     11There are lots of documents motivating this, feel free to review
     12them. [[http://www.kroah.com/linux/talks/ols_2002_kernel_codingstyle_paper/codingstyle.ps In short]]:
     13
     14* I want to read your code.
     15* I want you to read my code.
     16* I want to fix your bugs.
     17* I want you to fix my bugs.
     18
     19=== Further reading ===
     20
     21* [[http://perldoc.perl.org/perlstyle.html Perl coding style guide]]
     22* [[http://www.kroah.com/linux/talks/ols_2002_kernel_codingstyle_paper/codingstyle.ps Documentation/CodingStyle and beyond]], by Greg Kroah-Hartman
     23
     24== The basics ==
     25
     26=== Indentation ===
     27
     28Use 4 white spaces. No tabs, no 8 white spaces, no funny things. Fix your
     29editor's configuration, or use a decent one. There are plenty of them.
     30
     31=== Capitalisation ===
     32
     33Use lowercase for local variables, uppercase for constants.
     34
     35We have no convention for function names, and we have
     36functionNamesLikeThis (whichIHate), FunctionNamesLikeThis
     37(WhichIDontLikeEither) or function_names_like_this_one (which I
     38prefer). Do we need such a convention?
     39
     40=== Use meaningful names for globals, short names for locals ===
     41
     42<tt>i</tt> is a perfectly valid identifier for a loop variable. However, you
     43deserve nasty punishment if you use it as a function name.
     44
     45On the opposite, <tt>this_is_a_temporary_counter</tt> is plain bad for
     46a loop variable.
     47
     48So, some good examples:
     49
     50 sub write_user_credentials
     51 {
     52     # ...
     53 }
     54 
     55 for my $i (0..10) {
     56     # ...
     57 }
     58 
     59
     60And very bad examples:
     61
     62 sub wuc
     63 {
     64     # WTH???
     65 }
     66 
     67 for my $counter_from_0_to_10_included (0..10) {
     68     # Excuse me???
     69 }
     70 
     71
     72=== Be modular ===
     73
     74Really, it '''matters'''. Have you ever tried to understand a module which
     75is 700 lines long? Did you ever try to understand what's the purpose
     76of a variable that was defined 5 screens ago? Or the meaning of the
     7730th variable on this block? Or the purpose of that line that starts
     78and finishes beyond the screen?
     79
     80Here are the classic metrics for modularity:
     81
     82* No lines longer than 80 columns.
     83** Split or rearrange longer strings
     84** Split longer statements
     85* If you have more than 3 levels of indentation, split your block
     86* If your function goes beyond the screen, split it
     87** And don't try to reduce the font. ;)
     88* If you have more than 7 local variables, split your function.
     89** Sometimes it's OK to have 10, but if you have 15 your code is a problem.
     90
     91=== Don't use magic numbers  ===
     92
     93The <tt>constant</tt> pragma will give you meaningful names for any
     94values other than 0 and 1 you need. They'll help you to understand why
     95you chose such values on the past.
     96
     97Good example:
     98
     99 use constant PI => 3.141592;
     100 
     101 my $circle_area = $radio * PI * PI;
     102
     103And the bad example
     104
     105 # Oops! I missed a decimal somewhere!
     106 my $circle_area = $radio * 3.14159 * 3.141592;
     107
     108=== Comments ===
     109
     110When in doubt, don't use them.
     111
     112Comment all Pan data structures, or at least provide a link to their
     113full description. Try to use the new annotation syntax, which will be
     114(some day) imported by Doxygen, Javadoc or something similar.
     115
     116Comment the purpose of each file, probably using POD syntax. Write a
     117comment before the beginning of each function, telling what it does
     118and how to use it. But don't annoy the reader with the internals.
     119
     120Don't comment function bodies. If your code is so complex that it
     121needs further explanations, you should probably split it in several
     122functions and comment those functions. Of course, sometimes you have to work around some broken API, or some corner case. In such case, please comment '''why''' you are doing it, but not '''how'''. And don't comment the obvious.
     123
     124Bad examples:
     125
     126 ############################################################
     127 #
     128 # Increment by 1
     129 #
     130 ############################################################
     131 $i++;
     132 
     133 sub do_something
     134 {
     135    my @args = @_;
     136 
     137    # I do foo and bar here
     138    ...
     139    ...
     140    ...
     141    # And now, let's do bar again, but with a small difference
     142    ...
     143    ...
     144 }
     145
     146These are best done like this:
     147
     148 $i++;
     149 
     150 # Performs task foo...
     151 sub foo {...}
     152 # Performs task bar...
     153 sub bar {...}
     154 # Performs task baz, which is quite similar to bar
     155 sub baz {...}
     156 
     157 # Performs a set of tasks, returning blah blah with arguments
     158 # $arg1:
     159 # $arg2:
     160 sub do_something
     161 {
     162     my @args = @_;
     163     foo (@args);
     164     bar (@args);
     165     baz (@args);
     166 }
     167
     168
     169Finally, don't put any credits on your comments, excepting on file
     170headers.
     171
     172=== Don't use <tt>vars</tt> ===
     173
     174This pragma has been deprecated since Perl 5.6, and that's a long time
     175ago. Instead, use the <tt>our</tt> declaration for package-wide
     176variables:
     177
     178Good:
     179
     180 our @ISA = ...;
     181
     182Bad:
     183
     184 use vars qw (@ISA);
     185 @ISA = ...;
     186
     187=== Curly bracket position ===
     188
     189Follow Kernighan-Ritchie's convention: open curly brackets on the same
     190line as the sentence they belong to and close them on a line for their
     191own, excepting when it's an else or a do-while block:
     192
     193 if ($foo) {
     194     ...
     195 }
     196 
     197 while ($bar) {
     198     ...
     199 }
     200 
     201 if ($foo) {
     202     ...
     203 } else {
     204     ...
     205 }
     206 
     207 do {
     208     ...
     209 } while ($bar);   
     210
     211This way it is perfectly clear where each block starts, finishes and
     212continues.
     213
     214The only exception to this are the curly brackets that open a
     215function. They should be on a different line, and have nothing else on
     216the same line:
     217
     218 sub foo
     219 {
     220     my @args = @_;
     221 }
     222
     223The reason for this is that decent editors (f.i, Emacs) are aware that
     224such curly braces mean something special, and allow you to move to the
     225beginning and the end of a function with a single key stroke. This is
     226a great help for navigating code.
     227
     228=== Parenthesis ===
     229
     230Use them a lot. When in doubt, use them. Especially:
     231
     232* Always use parenthesis on function calls
     233* Always use them on function calls even when there are no arguments
     234
     235The reason is that:
     236
     237 foo();
     238
     239Is easier to understand than:
     240
     241 foo;
     242
     243=== Prefer methods over plain functions ===
     244
     245Especially when writing components, you want to log unexpected
     246things. It's free to have a <tt>$self</tt> object, and let it log.
     247
     248Only use plain functions if you want them exported to some other
     249module.
     250
     251== The Quattor library (AKA perl-CAF and perl-LC) ==
     252
     253=== Running commands ===
     254
     255Do not use backticks or <tt>system</tt>, nor use <tt>open</tt> for pipes. The
     256<tt>CAF::Process</tt> module has all you need, and it won't spawn new
     257subshells, which is much safer.
     258
     259Another advantage of the <tt>CAF::Process</tt> module is that your
     260command line will be automatically logged at verbose level, which
     261saves you snippets like this:
     262
     263 $self->debug (5, "Going to run ls -l");
     264 system ("ls", "-l");
     265
     266This will become inconsistent with different uses. Instead, do:
     267
     268 # Or any reporter object.
     269 my $proc = CAF::Proces->new (["ls", "-l"], log => $self);
     270 $proc->run();
     271 # One-line version:
     272 CAF::Process->new (["ls", "-l"], log => $self)->run();
     273
     274The log option is any CAF::Logger object, for instance the component
     275you are writing.
     276
     277==== Confidential information on the command line ====
     278
     279Sometimes you pass confidential data to your commands. For instance,
     280an encrypted password to usermod. In this cases, you don't want your
     281command logged. Just don't pass any log argument to CAF::Process::new:
     282
     283 my $proc = CAF::Process->new (["ls", "-l"]);
     284 $proc->run();
     285
     286==== Examples ====
     287
     288The following are extracted from CAF::Process man page, and assume you
     289don't want to log the commands.
     290
     291===== Piping to a process' stdin =====
     292
     293Create the contents to be piped:
     294
     295 my $contents = "Hello, world";
     296
     297Create the command, specifying $contents as the input, and "execute" it:
     298
     299my $proc = CAF::Process->new (["cat", "-"], stdin => $contents);
     300$proc->execute();
     301
     302===== Piping in and out =====
     303
     304Suppose we want a bi-directional pipe: we provide the command's stdin,
     305and need to get its output and error:
     306
     307 my ($stdin, $stdout, $stderr) = ("Hello, world", undef, undef);
     308 my $proc = CAF::Process->new (["cat", "-"], stdin => $stdin,
     309                                stdout => \$stdout
     310                                stderr => \$stderr);
     311 $proc->execute();
     312
     313And we'll have the command's standard output and error on $stdout and
     314$stderr.
     315
     316===== Emulating backticks to get a command’s output =====
     317
     318Create the command:
     319
     320 my $proc = CAF::Process->new (["ls", "-lh"]);
     321
     322And get the output:
     323
     324 my $output = $proc->output();
     325
     326=== File handling ===
     327
     328Writing to files is not as simple as one could think: there are risks
     329that you should be aware of. For instance, the following code is an
     330example of what shouldn't be done:
     331
     332 open (FH, ">/tmp/foo");
     333
     334If /tmp/foo already exists and is a symbolic link to /etc/shadow, you
     335just lost all accounts on your system.
     336
     337==== Writing to a file ====
     338
     339Don't open files directly. Use the CAF::FileWriter module to do the
     340work for you:
     341
     342 my $fh = CAF::FileWriter->open ("/my/file", log => $self);
     343 print $fh "Hello, world!\n";
     344 $fh->close();
     345
     346This module will perform all security checks for you, and will log
     347what files you open for writing, so that your configuration is easier
     348to debug.
     349
     350You can specify the permissions you want at instantiation, too:
     351
     352 my $fh = CAF::FileWriter->open ("/my/path", mode => 0400, log => $self );
     353 print $fh "Hello, world!\n";
     354 $fh->close();
     355
     356If you find any errors and have to discard your contents, just call
     357the cancel method, and the existing file will be preserved:
     358
     359 my $fh = CAF::FileWriter->open ("/my/path", mode => 0400, log => $self);
     360 print $fh "Hello, world!\n";
     361 if ($error) {
     362     $fh->cancel();
     363 }
     364 $fh->close();
     365
     366Finally, if the contents you generate are the same as on the original
     367file, nothing will be done, which will keep timestamps, among other
     368properties.
     369
     370The man page has all the details and more examples.
     371
     372==== Temporary files ====
     373
     374Don't use them. If you don't use File::Temp, you'll use predictable
     375filenames, and that's just bad. Then, most implementations make
     376temporary files world readable, and you usually don't want that. If
     377you need temporary storage for some text, use an array, IO::String,
     378in-memory files, a CAF::FileWriter or anything like that.
     379
     380So you want to run a command which needs a file name as an argument,
     381right? Easy. Just pipe to that command, as shown above. And pass
     382/dev/fd/0 as the file name.
     383
     384Finally, if all these options are not good enough (what are you trying
     385to do?), use File::Temp::tmpfile, which will provide you an anonymous
     386file handle. But please, use this only if you are convinced there is
     387no other way to keep your temporary data.
     388
     389==== Modifying an existing file ====
     390
     391Sometimes you just want to add a couple of lines to an existing file,
     392without destroying its current contents. Or to remove them. The module
     393<tt>CAF::FileEditor</tt> is just for that. It is a subclass of
     394<tt>CAF::FileWriter</tt>, and when you call the <tt>string_ref</tt>
     395method you'll get a reference to the file's contents so that you can
     396freely update them.
     397
     398For instance, you only want to change the first "a" for a b on the
     399first line of a file:
     400
     401 my $fh = CAF::FileEditor->open ("/foo/bar",
     402                                 log => $self);
     403 my $ref_to_contents = $fh->string_ref;
     404 $$ref_to_contents =~ s{a}{b};
     405 $fh->close();
     406
     407If you want to add text at the beginning of the file, the
     408<tt>head_print</tt> method is what you are looking for.
     409
     410==== Other tasks with files ====
     411
     412When in doubt, use <tt>LC::</tt> modules and functions, over standard ones. For instance, use <tt>LC::File::copy</tt>, <tt>LC::File::move</tt> instead of <tt>File::Copy</tt> and friends.
     413
     414<tt>LC::</tt> functions perform more security checks and are less prone to symlink attacks.
     415
     416== Input handling ==
     417
     418Make your code run in tainted mode.
     419
     420This is, don't trust any inputs, even if they are from the
     421profile. During the workshop, Nick proposed to sign profiles (it was proposed 2 years ago, too). While this gets (or not) implemented, '''profiles are not to be trusted'''. Sanitise everything just after reading it, and that way
     422you'll be able to use it freely.
     423
     424Also, when you print files that will be sourced by shell scripts, be
     425sure to print all values between single quotes. This is true for
     426almost every file you have under /etc/sysconfig.
     427
     428== Quality of messages ==
     429
     430=== Don't annoy the user ===
     431
     432The main principle is not to annoy the user. Use <tt>info</tt> and
     433<tt>ok</tt> methods only for '''very''' important messages. A small
     434description of the tasks you are going to perform, if they are complex
     435enough.
     436
     437=== Unless he asks to ===
     438
     439Provide a detailed trace of any task you perform with
     440<tt>verbose</tt> messages. Trace DNS queries, URLs being retrieved,
     441services you have to enable or disable...
     442
     443But you don't need to log files you open or commands you run. The
     444appropriate CAF objects will do so for you.
     445
     446=== Debugging output ===
     447
     448Don't print user-relevant messages with <tt>debug</tt>. This should be
     449used only for developer-relevant stuff, such as tracking temporary
     450contents or so.
     451
     452We don't have any convention for debug levels, is it needed?
     453
     454=== Use <tt>error</tt> only for fatal errors ===
     455
     456Use the <tt>error</tt> method only if this error will make the entire
     457component fail. If you can handle it, or the fail is not really
     458important for the component's results, use <tt>info</tt> instead. If
     459failing to download a file is OK because you can work around it, or
     460you know you may have no rights to write on AFS, but that's OK, use an
     461<tt>info</tt> message.
     462
     463Otherwise, the component will be re-run with no need, and will keep
     464failing and triggering alarms on the user's monitoring system.
     465
     466== Conclusions ==
     467
     468Code quality matters. It will reduce bugs, and will make everybody's
     469life easier.
     470
     471All these conventions can be improved, so feedback will be
     472appreciated. Especially, the CAF library can be extended with whatever
     473task we repeat over and over (f.i, do we need any especial code to do
     474DNS queries or download files from the Internet?).
     475
     476[[User:Luisfmunoz|Luisfmunoz]] 00:19, 12 January 2009 (UTC)