Geary Vala Coding Conventions

Geary is written in Vala. The following guidelines are shaped toward describing our preferred coding style for Vala.

The goals of these coding conventions are consistency and readability for a team of coders, in the service of producing quality code. There are few hard-and-fast rules here. Use good judgement when stretching their boundaries or violating them outright. Ask if you’re unsure.

Formatting

Ordering

The members of each class should appear in the following order:

  1. consts
  2. enums
  3. inner classes
  4. static properties
  5. static fields
  6. instance properties
  7. instance fields
  8. signals
  9. constructors
  10. destructor (optional)
  11. methods

Within them, members should be blocked in order of:

  1. public
  2. protected
  3. internal
  4. private

Basics

  • Source lines must be no more than 80 characters long. Exceptions are made for the occasional couple of characters over. Note: You can configure gedit to display a margin characters; that may help you follow this convention.
  • Functions or methods longer than about 50 lines are discouraged. This is about the amount of code that can be easily viewed on a tall computer monitor. A function or method longer than 100 lines should certainly be split up if reasonably possible.
  • Always use the this prefix for accessing the current instance's properties, signals and other members, but don't use it for invocation, e.g. method calls, signal calls, etc. This makes the scope immediately obvious:

public class Foo {

    private string? bar = null;

    public signal void baz();

    public Foo(BazListener baz_sink) {
        this.baz.connect(baz_sink.on_baz);
    }

    private void frob(string barish) {
        ...
        baz(this.bar);
        debug("Just bazzed: %s/%s".printf(this.bar, barish));
    }
}
  • Variables should be declared at the point where they are first needed rather than at the top of a block or function. If the variable is declared when needed, it often can be initialized with a useful value. Vala initializes all variables, but they still should be initialized explicitly unless delayed due to a conditional.

Right:

int x = get_x();
int y = get_y();

int width, height;
if (is_tall()) {
    width = 0;
    height = 100;
} else {
    width = 100;
    height = 0;
}
  • The var keyword may only be used where the type can be determined from the current source file by a single inference step. For example, inside a foreach where the type of the collection is declared as an argument of a method. If another source file must be consulted to determine the type of the variable, or if the type must be determined by determining the type of another var variable, then it must not be used.

Right:

Gee.Set<Geary.Email.Identifier> extract_email_identifiers(Gee.Collection<Geary.App.Conversation> targets) {
    var ids = new Gee.HashSet<Geary.Email.Identifier>()
    foreach (var target in targets) {
        ids.add_all(target.get_email_ids());
    }
    return ids;
}

Wrong:

class ConversationList {

    ...

    void update_email_parts() {
        var all_email = get_selected_email();
        all_email.add_all();
        // Wrong: Double inference from email to all_email
        foreach (var email in all_email) {
            // Wrong: Email class is declared in another file
            foreach (var part in email.parts) {
                ..
            }
        }
    }

}
  • Use named constructors. Avoid construct blocks when possible.

  • Don’t use the using statement. Explicitly preface each symbol with its namespace name. For example, Gtk.Window rather than just Window.

    • Symbols within the code's namespace can use Vala's implicit namespace rules.
    • Note that although Vala imports the GLib namespace automatically, this might change in the future, so it is best to always explicitly use the GLib namespace.

Naming

  • Follow Vala conventions for naming identifiers.

    • Variables and class fields are lowercase_with_underscores.
    • Namespaces and class names are CamelCased.

  • Source filenames are determined by each project; our current scheme in Geary is to use namespace-class-name-with-dashes.vala. To avoid long filenames, use the namespace above the class name only. Thus:
    • Geary.EmailIdentifier is located in geary-email-identifier.vala

    • Geary.Imap.MailboxSpecifier is in imap-mailbox-specifier.vala.

  • Use meaningful parameter and variable names:
    • Indicate what the instance actually is, don't just repeat the class name.

    • Don't use several-character abbreviations, use a full word.

Right:

Gtk.Window main_window;

Discouraged:

Gtk.Window w;
Gtk.Window window;

  • Single-character variable names (e.g. w) are allowed for local variables only where its use is obvious (i and j for counters, x and y for coordinates, etc.)

Braces

  • An opening brace appears on the same line as preceding code.

Right:

void foo() {
    for (int x = 0 ; x &lt; 10 ; ++x) {
        ...
    }
}

Wrong:

void foo()
{
    for (int x = 0 ; x &lt; 10 ; ++x)
    {
        ...
    }
}

  • A brace preceding or following an else keyword appears on the same line as the else. If a single if-else if-else block is braced, all should be braced.

Right:

if (x == 4) {
    ...
} else {
    ...
}

Wrong:

if (x == 4) {
    ...
} else ++y;

if (x == 4) {
    ...
} else
    ++y;

if (x == 4) {
    ...
}
else {
    ...
}

  • A brace preceding a catch keyword appears on the same line as the catch.

Right:

try {
    ...
} catch (Error e) {
    ...
}

  • A value in a return statement is not parenthesized.

Right:

return x;

Right:

return a + b;

  • The body of an if, for, while or similar statement should always be surrounded by braces, and the body should always start on the following line.

Right:

if (x == 3) {
   ++x;
}

Wrong:

if (x == 3)
   ++x;

if (x == 3) ++x;

Spacing

  • Spaces, not tabs, for indentation.
  • Indent each nesting level by 4 characters.
  • There should be no trailing whitespace on any line, including on blank lines between indented statements.
  • There should be no successive blank lines in code. One exception is in larger classes, between blocks of member types, where two blank lines may be used, e.g.:

.
.
.
public string foo = null;


public signal void bar();
.
.
.
  • If a namespace block encloses an entire source file, then definitions inside the namespace are not indented. If a namespace block encloses only part of a file, definitions inside it are indented.
    • In general, it's best to enclose the entire source file in a single namespace. If code needs to be outside the namespace, consider using a different source file.
  • Use spaces around binary operators.

Right:

a = x + y;

if (x == y)
    ...

Wrong:

a = x+y;

if (x==y)
    ...

  • There should be no space between the ++ or — operator and a variable name.

Right:

++i;
x = j++;

Wrong:

++ i;
x = j ++;

  • Unary operators such as ! and ~ are not followed by a space.

Right:

left = !right;
stopped = !process.is_running();

Wrong:

left = ! right;
stopped = ! process.is_running();

  • In an array declaration and access, there should be no space between the array name and the opening left bracket.

Right:

int i[10];
x = i[4];

Wrong:

int i [10];
x = i [4];

  • In a type cast, there is a space following the right parenthesis.

Right:

int i = (int) f;

Wrong:

int i = (int)f;

* Keywords such as if, for, and while are followed by a space, then a left parenthesis.

Right:

if (x == 4)
    return;

Wrong:

if(x == 4)
    return;

if( x == 4 )
    return;

  • In a variable declaration, a single space appears between the type and variable name.

Right:

int count;
bool is_complete;
Window main_window;

Wrong:

int    count;
bool   is_complete;
Window main_window;

  • In a method call or declaration there is no space between the method name and following left parenthesis, and no space appears after the left parenthesis or before the right parenthesis.

Right:

int add(int a, int b) {
    ...
}

int i = add(4, 5);

Wrong:

int add (int a, int b) {
    ...
}

int i = add ( 4, 5 );

  • A single blank line should appear between each pair of functions or methods.
  • A single blank line should appear between a field declaration and a following constructor or method.
  • There should be no blank line after an opening brace or before a closing brace. An exception is after the opening brace in a namespace statement, and before its closing brace:

namespace City {

class House {
    int xyz;
    ...
}

}

  • Continued lines receive a single additional indent. Do not indent to line up method arguments with the line above them.

Comments

  • Comments are encouraged. However, if code requires comments, consider restructuring your code.

  • Valadocs are strongly encouraged in library code (i.e. the Geary engine) and preferred for public symbols in the client code. See section on Valadocs, below, for detailed information.
  • Checking in commented-out code is strongly discouraged.

  • In a single-line comment, the opening characters // should be preceded by one or more spaces, and should be followed by a single space. Trailing comments are discouraged and should be used sparingly. Comments preceding code lines are preferred:

Acceptable:

int count; // number of widgets in the conglomeration

Better:

// number of widgets in the conglomeration
int count;

Wrong:

int count; //number of widgets in the conglomeration
int count;// number of widgets in the conglomeration

Language Features

Casting

  • Vala does implicit type-checking of method arguments when the --enable-checking compile flag is set. Geary uses this flag.

  • There are three operators related to casting in Vala: is, as, and C-style casting (i.e. Object m = (Object) n;). Their proper use is important.

  • is and as can only be used with non-compact classes. Casting of native types (i.e. int, char, etc.) can only be done with C-style casts.

  • The is operator returns true if the object inherits from or implements the specified type. Note that is also returns true if the object implements a particular interface:

if (obj is Gtk.Widget)
    // do something

  • The is operator can also be used to check the domain and error code of an Error (exception), or merely its domain:

try {
    ...
} catch (Error err) {
    bool is_cancelled = err is IOError.CANCELLED;
    bool is_ioerror = err is IOError;
}

  • The as operator is a “safe” cast in that it returns null if the object is not of the specified type (or does not implement the specified interface). Because of this behavior, the returned type should be marked nullable and tested for null before use:

Gtk.Widget? widget = obj as Gtk.Widget;
if (widget != null) {
    widget.show_all();
}

Or using var (note it is implicitly nullable, and still needs to be tested as such):

var widget = obj as Gtk.Widget;
if (widget != null) {
    widget.show_all();
}

Wrong:

Gtk.Widget widget = obj as Widget;
widget.show_all();

  • C-style casting is not as “safe” as the as operator. It does perform GType checking before returning and will dump a warning to the console and return null when a bad cast occurs. However, see the next point for why a nullable type is not used in a C-style cast.

Right:

Gtk.Widget widget = (Gtk.Widget) obj;
widget.show_all();

Wrong:

Gtk.Widget widget = (Gtk.Widget) obj;
if (widget != null) {
    widget.show_all();
}

  • When casting an object, use as for a test-and-use pattern and use C-style casting when the object should be of a certain type (i.e. no test is required). In other words, use a C-style cast when expecting non-null, hence a nullable type is not required.

  • assert() is often used to check parameters or return values for typeness. Do not use assert with the is operator prior to an as operation or a C-style cast; both defeat the purpose of Vala’s casting model.

Wrong:

assert(obj is Gtk.Widget);
Gtk.Widget widget = (Gtk.Widget) obj;

  • Non-compact classes in Vala also have the get_class method which returns GType information, which can then use used for type checking and testing. This should only be used in specific circumstances where the Vala operators are insufficient or unavailable. The typeof operator is also useful here.

Switch statements

  • In a switch statement, a case should be indented at the same level as the switch to avoid double-indentation, and break on the same level as the code section. A return or continue should be indented as regular code.

  • If the switch statement is for an enum, Vala can perform type inference and hence case statement values should not be prefixed with the type
  • There should be a blank line between each case block.
  • A case block should not be braced.
  • An empty case block should contain a comment explaining why it's empty; "do nothing" is sufficient to indicate that it's presence is merely to allow flow of control through the switch statement without any side-effects.

Right:

enum Suite { SPADES, …; }

Suite current = …;

switch (current) {
case SPADES:
    do_something();
    break;

case HEARTS:
case DIAMONDS:
    do_something_else();
    return;

default:
    // do nothing for other suits
    break;
}

Wrong:

switch (suit) {
    case Suit.SPADES:
        do_something();
        break;

    case Suit.HEARTS:
        do_something();
    return;
    default:
    break;
}
  • Note that Vala doesn’t allow falling out of a case block without a jump statement of some kind, even if it’s the last block in the switch.

Signals

  • Signals should be declared like methods, i.e. a blank line between each declaration.

Right:

public signal void plain_signal();

public virtual signal void virtual_signal() {
}

public signal void another_plain_signal();

Wrong:

public signal void plain_signal();
public virtual signal void virtual_signal() {}
public signal void another_plain_signal();
  • Signal handler methods should have descriptive names beginning with on_.

Right:

search_menu.activate.connect(on_search_menu_activated);

  • Signals should only be directly fired by the class itself. In rare cases it’s okay for a subclass to fire a signal. Having code external to the class directly fire its signals suggests a design flaw.
  • Our projects often use notify_ wrappers for firing signals. These are specific to core classes that use signals extensively and are presumed will be subclassed, and the subclasses will need to monitors signals for their own synchronization purposes. In this case, the notify wrappers are always protected virtual methods (as they should only be called by the base class and overridden by its children).

Right:

public signal void count_changed(int new_count);

protected virtual void notify_count_changed(int new_count) {
count_changed(new_count);
}

This allows for a subclass to override notify_count_changed and update its internal state before or after the signal has fired.

Properties

  • Properties should be constrained to simple getter/setters for public values.
    • If the value is not set in the constructor, it should be set using the default keyword, even if the default is the natural default for that value (i.e. 0, 0.0, or null).

    • If a simple property is only a getter, mark it as private set.

    • Avoid construct properties.

  • Like methods and signals, leave a single blank line between each property declaration.
  • If a property has no code blocks, its modifiers should be listed on a single line. If it has any code blocks, they should be listed as indented separate code blocks:

Right:

public int apple_count { get; private set; } // default set in constructor

public int orange_count { get; set; default = 0 }

public int banana_count {
    get {
        return (banana_list != null) ? banana_list.size : 0;
    }
}

private Gee.List<Banana>? banana_list = null;

  • A property code block should be no more than 2 - 3 lines of code. Anything longer strongly suggests a hand-coded getter and/or setter is required.
    • GObject provides the "notify" signal for detecting when a property has changed; Vala’s syntax is notify["property-name"].)

  • When a property is “shadowing” a private field (as is commonly required for abstract properties), the private field is listed immediately after the property itself (that is, with no whitespace), so that API documentation comments (see section below) can be provided. Otherwise private member instance variables should be listed after the property declarations.

/** {@inheritDoc} */ 
public Foo current_fooish {
    get {
        return this._current_fooish;
    }
}
private Foo _current_fooish = null;

Closures (anonymous methods)

In general, closures should only be used to implement short “glue” code blocks to hook up callbacks and signal handlers with class methods. They allow adapting the callback's API (parameters and return type) to the class's API, without needing to implement an additional method for that. This keeps related code together, making it easier to understand and maintain.

  • Closures should ideally be a single line of code, and usually no more than 2 - 3 lines of code. Anything longer strongly suggests a separate method should be used.
  • However, if a system by design uses delegates and is more convenient and clean to take advantage of Vala's closures, then so be it. One example is Geary's Db module, where closures are passed to the database transaction methods and are called by a separate thread.
  • Use full parameter names in the closure declaration, as though it was a “real” method. If the parameters are not required, do not declare them.
  • If the closure is a single line of code, write it that way. If it requires multiple statements, list them as though a “real” method.

Right:

window.destroy.connect(() => { debug("destroyed"); });
window.draw.connect((ctx) => {
    debug("draw");
    ctx.clear();
});

Wrong:

window.destroy.connect((window_param) => {debug("destroyed");});
window.draw.connect((ctx) => {
    debug("draw"); ctx.clear();
});

Default arguments

Vala supports default arguments. As convenient as this feature can be, it can also introduce bugs.

  • Public methods can use default arguments when they make sense. Private methods should not use default arguments.

  • One exception to this is public methods in a private or internal class. Since those methods are public only to other classes in the namespace, they are treated as “private” in terms of default arguments (i.e. should not have default arguments).

  • Cancellables are an important use of default arguments, especially in Geary. Cancellable defaults to null throughout GIO as an accepted practice. As with the above guideline, if the method is public, it can default to null; otherwise, it should not.

Other language features

  • Using the ternary operator in place of simple if-else blocks is strongly encouraged.
  • Remove any unnecessary automatic variables when possible.
  • Remove or revise less-than-useful comments.

Valadoc

We’re making a concerted effort to document the Geary engine with Valadoc, and having documentation comments for public symbols in Geary's client is preferred. Similar to Javadoc, Valadoc uses specially-formatted comments that can be parsed by tools to auto-generate documentation.

  • A reference guide for Valadoc markup can be found at https://valadoc.org/markup.htm

  • All public, internal and protected symbols (that is, classes, methods, and namespaces) should be Valadoc'ed.
    • Private symbols (including public symbols on private classes) do not need documentation comments, but of course comments are always encouraged and you may as well use the documentation comment format for consistency
  • If a symbol is an override or an interface implementation of a symbol documented in its parent class or interface, use the inheritDoc taglet:

/**
 * {@inheritDoc}
 */

Additional notes about the symbol should follow the taglet.

  • Other taglets are optional. Use discretion. The see taglet can be very useful.

  • Valadocs for Geary can be produced with this command in the source code:

$ ninja -C $BUILD_DIR valadoc

This will generate HTML API documentation in the $BUILD_DIR/src/valadoc directory.

Basics

* A protypical Valadoc for all types of code objects looks like this:

/**
 * A single line summary with a period.
 *
 * Longer (and, for simple code objects, optional) summary explaining
 * the class/method/property in greater detail. Like code lines, the
 * summary lines should not exceed 80 characters per line.
 */

  • The summary line is required. The longer summary is optional, but only for the simplest of methods (usually properties).
  • Use Valadoc markup whenever appropriate. The first reference to a symbol should always be linked (i.e. @link Geary.EmailIdentifier), even if the symbol is the current class. The reason for this is that the current Valadoc generator doesn't automatically generate links to the symbol's container.

* Use HTML links whenever appropriate if the code is based off a specification or foreign API.

Classes

Each class should have a summary Valadoc. Place a single blank line between its Valadoc and the class declaration.

/** 
 * A representation of the IMAP APPEND command.
 *
 * See [[http://tools.ietf.org/html/rfc3501#section-6.3.11]] 
 */
public class Geary.Imap.AppendCommand : Command {

Other symbols

  • Methods, properties, enums, and consts should also have Valadoc. There should be no blank line between the Valadoc and the declaration.

/**
 * Returns the direction of the overhead oscillating fnortiner.
 */
public bool clockwise { get; private set; }
  • To document enums:

public class Fnortiner {
    /**
     * Describes the {@link Fnortiner}’s position in the {@link Gizmo}.
     */
    public enum Position {
        /**
         * Indicates the {@link Fnortiner} is upside-down.
         */
        NORMAL,
        /**
         * Indicates the {@link Fnortiner} is inverted.
         */
        REVERSED
    }
}

Note how the containing class is linked in the Valadoc.

Apps/Geary/CodingConventions (last edited 2020-10-11 00:51:54 by MichaelGratton)