10.3. Coding In YCP

As with any other programming language, there are some rules of “DOs and DON'Ts” in YCP too. During the development of the YaST installer the developers realized that some particular ways of doing things are usually better than some others. Furthermore some “standards” have been worked out, that (if heeded) make the resulting code uniform to some extent which makes it much easier to understand code written by other people. The following links give some hints that should ease programmers life.

10.3.1. Coding Rules

 

Any fool can write code that a computer can understand. Good programmers write code that humans can understand.

 
 --Martin Fowler in: Refactoring, improving the design of existing code

Having multiple developers working on the same source code needs a basic set of coding rules to adhere to. A proper code layout makes it a lot easier for others to read, understand, enhance, debug, and clean-up code.

Having a coding style is quite common, two of the more prominent examples are The Linux kernel coding style and the GNU coding standard

The document on hand describes how to lay our code written in YCP. How to name your variables and functions, how to place braces, and how to indent blocks.

Every programmer usually has her/his own style of writing code. The rules presented here might not match your personal preferences, but will help to work on the code as a team. Helping out and fixing bugs will be easier with a common coding style.

The following set of rules tries to be complete, but probably isn't.

These rules should apply to C, C++, and YCP code alike, even though the examples use YCP.

10.3.1.1. The file header

Every file must begin with a proper header. This header should be started within the first 10 lines of the file, so it is visible when loading the file in an editor.

The file header must include

  • the file name

  • the file purpose summary (in one line !)

  • the authors name and email address

  • the CVS $Id: coding-rules.xml,v 1.1 2004/10/03 16:54:44 nashif Exp $

  • a few lines description about the contents

The '$Id: coding-rules.xml,v 1.1 2004/10/03 16:54:44 nashif Exp $' will be replaced automatically when the file is handled by CVS. Don't touch this line afterwards, it's controlled by CVS then.

DoDon't
/**
 * File:
 *   io.ycp
 *
 * Module:
 *   Security configuration
 *
 * Summary:
 *   Input and output functions.
 *
 * Authors:
 *   Michal Svec <msvec@suse.cz>
 *
 * $Id: coding-rules.xml,v 1.1 2004/10/03 16:54:44 nashif Exp $
 *
 * There are in this file all functions needed for
 * the input and output of security settings.
 */

{
  // a small example with no version and no hint
  // about the author.
  return 42;
}

10.3.1.2. Indendation

Among developers, indentation of code is one of the most heated points of discussion. There are several 'good' ways to use whitespace when writing sourcecode, all are 'right' in some respect.

The only bad indentation is no indentation at all. To make code easy to read, a common way of using whitespaces is needed across a team of developers:

  • indent by 4 spaces

  • tabs are 8 spaces

  • always indent

Only a few lines of a file are allowed to be not indented. These are the initial comment lines of the file header and the opening and closing braces around the code.

DoDon't
/*
 ... initial header
 */

{       // opening brace at start of code

    // my first variable, 4 spaces indentation

    integer first_int_variable = 42;

    if (first_int_variable > 42)
    {
        // 8 spaces (== 1 tab character) indentation
        doSomething ();
    }
    else
    {
        somethingDifferent();
    }

    // final return

    return first_int_variable;

}       // closing brace
        /* ... initial header  */

{       // opening brace at start of code

// my first variable, bad indentation

integer first_int_variable=42;

if(first_int_variable>42) doSomething ();
else somethingDifferent();
return first_int_variable;}

10.3.1.3. Whitespace

Whitespaces (blank, tab, and newline characters) are allowed anywhere in the code. Proper use of whitespace does make code a lot easier to read and more pleasing to the eye.

Blanks are mandatory at the following places:

  • before an open parantheses

    • at function calls

    • at if and while expressions

  • after a comma

    • at parameter lists in function calls

    • at list and map elements

  • before and after a binary operator. '=' is a binary operator

Newlines are mandatory at the following places:

  • before and after every opening brace.

  • before and after every closing brace.

  • after the initial variable declarations, before the first statement.

  • to separate functional groups. a functional group is this sense is a set of variable declarations before a group of computational statements. another example is grouping in pre-processing, computing, and post-processing often used in larger modules.

Feel free to use whitespaces at other places you find appropriate.

Some explanation to the above "Don't" example:

  • there is no blank before the "(" in the if, while, and callFunction lines.

  • there is no newline to properly separate the group of variable declarations from the computational statements.

  • there are two variable declarations in the if () block. Without whitespace this isn't easily visible.

DoDon't
    if (bool_flag) ...

    while (stay_in_loop) ...

    callFunction ( value1, value2 );

    list a_list = [ 1, 2, 3, 4 ];
    map a_map = [ 1:`first, 2:`second ];
    boolean test_flag = true;

    if (test_flag)
    {
        integer one = 1;
        boolean two_flag = false;

        callFunction (one, two_flag);
    }
    if(bool_flag) ...
    while(stay_in_loop) ...
    callFunction(value1,value2);
    list a_list=[1,2,3,4];
    map a_map=[1:`first,2:`second];


    boolean test_flag=true;
    if (test_flag){
        integer one=1;boolean two_flag=false;
        callFunction(one,two_flag);}

10.3.1.4. Naming of variables

This rule should be easy if you keep in mind that other developers want to read and understand your code.

General rule: Use speaking variable names. By reading the name of a variable it should be immediately clear

  • what kind of value variable represents

  • how the variable is used

  • probably its scope

There is no restriction in the length of a variable, use this fact!

To make a clear destinction of variable names vs. function names, use '_' in variables and upper/lower case in function names.

DoDon't
    boolean is_sparc = (architecture == "sparc");
    list probed_modems = SCR::Read ( .probe.modem );
    integer list_index = 0;
    map a_modem = $[];

    while (list_index < size (probed_modems))
    {
        a_modem = select (probed_modems, list_index);
        doSomething (a_modem, is_sparc);
        list_index = list_index + 1;
    }
    boolean n = (architecture == "sparc");
    list dev = SCR::Read(.probe.modem);
    integer i = 0;
    map m = $[];

    while (i<size(dev))
    {
        m = select (dev, i);
        func (m, n);
        i=i+1;
    }

10.3.1.5. Naming of functions

Like variables, function names should speak for themselves. So the above arguments for naming variables apply also to functions.

But instead of '_', use mixed upper and lower case to make the names 'speak'.

It is also helpful to distinguish between global and local functions. Local functions should start with a lower case letter, global functions with an upper case letter.

DoDon't
    // this is a local function
    writeStringToFile (a_string, file_name);

    // this is a global function
    global_settings = ReadGlobalSettings ();
    f1 (a_string, file_name);
    gs = rgs();

10.3.1.6. Blocks and Braces

There are more ways to place braces around a block than there are computer languages which use '{' and '}'.

For YaST2, only two rules about braces are important:

  • the opening and closing brace are on the same indentation level.

  • the opening brace increases the indentation level by one (which equals 4 spaces).

10.3.1.7. if-then-else, while, etc.

This one is really easy, always use a block for if and else cases and while statements.

DoDon't
// start of file
// first brace doesn't have any indentation
{
    // 4 spaces indentation
    integer initial_index = 0;

    while (initial_index < 10)
    {
        // incremented indentation level
        initial_index = initial_index + 1;
    }

    return initial_index;
}
{integer initial_index = 0;
while (initial_index < 10){
initial_index = initial_index + 1;}
return initial_index;}

10.3.1.8. Comments

Comment every function with a structured comment. The comments will be used for the documentation generation. The syntax is similar to ydoc (kdoc).

/**
 * Update the SCR from the map of all security settings
 * @param settings a map of all security settings
 * @return boolean success
 */

define SecurityWrite(map settings) ``{
    ...
}

10.3.1.9. Other habits

This is a small list of other things to consider when writing code.

  • Superfluous whitespace in the source. This was discussed on the linux kernel mailing list, see Kernel Traffic #103 For 19 Jan for more. For vi users, adding:

    syntax on
    let c_space_errors=1
    

    should help. In Emacs 21, set the variable show-trailing-whitespace, also see whitespace.el

  • Replace blanks with tabs. Since a tab character equals 8 blanks, multiple blanks should be replaced by tabs.

10.3.2. Examples of bad code

BadGood
  any ret = boolean_function ();

  if (ret)
  {
        ....
  boolean ret = boolean_function ();

  if (ret)
  {
        ....

Here the type for "ret" is known, since it is used as a boolean in the if expression. So don't use "any" as a type declaration is you know better.

BadGood
  term|any x = nil;

  if (boolean_value)
  {
      x = `VBox (...);
  }
  else
  {
      x = `Empty ();
  }
  term x = `Empty ();

  if (boolean_value)
  {
      x = `VBox (...);
  }

Why invent an extra "else" case for a simple expression?

OK, for complex things where one can't say 'this will be used 90% of the time', an else-case is needed. But then it's still better to initialize the variable with a dummy value of the expected type (i.e. term x = `Dummy();) instead of "nil".

BadGood
  any ret = UserInput ();
  ...
  return (ret == `ok);
  symbol ret = UserInput ();
  ...
  return (ret == `ok);

Similar to the "boolean" example above, we know the type for "ret" in advance.

BadGood
  any|list x = list_function ()
  if ( x != nil && select (x, 1) == true)
  {
        ....
  list x = list_function ()
  if ( x != nil
      && (size (x) > 1)
      && (x[1]:false == true))
  {
        ....

This is a particulary bad example since the code was plain wrong initially. The list variable was just tested for "nil" but expected to have two elements.