Skip to content
Justin Blanchard edited this page Oct 2, 2021 · 5 revisions

Introduction

The exact coding style for Naev has been asked before. First off let's mention the two golden rules of Naev coding development:

  • We want to welcome rather than scare off contributors. Therefore we are not really strict. However if you do a good job following our coding style you shall be praised for it.
  • It is FUNDAMENTAL that everything be clearly documented. We want new and veteran developers to be able to rely on generated API docs. 1
  • We want readable and well documented code. Even if you've done an awesome optimization, if we can't understand it, chances are it'll get rewritten or modified in a way that creates bugs. So please focus on readability first and then optimizations second.

C Code

Doxygen

To help with this it's generally a good idea to use Doxygen. For example we can do:

/**
 * @brief Checks if a foo_t is bar.
 *
 * Longer explanation of what exactly this does and how it does it here.
 *
 * @note foo must not be NULL.
 *
 *    @param foo foo_t to check if is bar.
 *    @return 1 if foo is bar, 0 otherwise.
 */
int foo_isBar( foo_t foo )
{
   return (foo == bar);
}

This is a clear way to document. However, Doxygen does not exclude you from documenting the code also. Especially if it's a complicated part.

Many functions in the nlua_*.c source files (and ai.c) are exported to Lua. Their doc comments look different, because they're auto-translated for use by LDoc. Directives like @luatparam, @luareturn, and @luafunc correspond to LDoc's @tparam@, @return, @func.

Indentation

We use 3 spaces instead of tabs. This can be achieved by putting the following in your ~/.vimrc:

set tabstop=3           " indents
set softtabstop=3       " treat 3 spaces as a single character (when deleting)
set shiftwidth=3        " more indents
set expandtab           " use spaces instead of tabs

Function Declarations

Generally we declare functions as:

type function( type1* param1, type param2 );

However if there are no parameters we do it as:

type function (void);

With brackets we do:

type function (void)
{
   some_code();
}

Variables

Variable declaration statements themselves follow the same style as function parameters, except for pointers. Instead of type1* param1, we place the * symbol next to the symbol, to make compound declarations clearer: type *pointer, not_pointer;

Naev's tradition is to declare variables at the top of the function right after the opening bracket. This is fine, but for newer code the preference is to declare variables at the start of a scope:

void function (void)
{
   for (int i=0; i<N; i++) {
      int cats_seen = 0;

      some_code();
   }
}

Conditional Expressions

We use the following formatting:

if (something) {
   do_something();
}

However if it's more readable and there's only one line we can avoid brackets:

if (something)
   do_something();

If there are multiple statements it is recommended to use explicit parenthesis:

if ((some_value == 0) && (some_pointer != NULL))
   do_something();

It is recommended to compare against NULL instead of just checking the pointer. For example:

if (some_pointer != NULL)

instead of

if (some_pointer)

As it's more explicit on the type.

Loops

We prefer to check against conditions instead of for conditions to avoid indentation. For example:

for (i=0;i<N;i++) {
   if (!foo_isBar(&foo[i]))
      continue;
   DEBUG( "foo is bar" );
}

This helps make code more readable.

Arrays

Many data structures include variable-sized arrays. Naev has a standard tool for this declared and documented in array.h. Any time you have two variables representing an array and its size, consider using this tool.

Lua

translatable string formatting, where to put mission text (vs. what's traditional), how and when to use vn.

Mission/Event Scripts

Missions and events are powered by Lua scripts containing an XML comment and a standard set of function definitions. For an example, see docs/missions/mission_template.lua. Naev developers have learned some lessons along the way; not all scripts are going to be perfect role models. Best practices include:

  • Always use gettext for user-visible text. Naev provides 3 flavors to all Lua scripts _("text") translates a string. N_("text") is a no-op (does not translate), but marks its input as a translatable string. n_("%d cat", "%d cats", n) translates an expression which may be plural.
  • Keep the global variables clear and simple. Naev will preserve active missions' variables in saved games (except not tables, except yes tables with the __save field set). If you refactor the way your globals work, users` active missions might break.
  • Prefer to put the text in-line. (There's a widespread pattern of putting all the strings at the top of the file, sometimes in tables with names like misn_desc. This was done with good intentions, but it tends to backfire.)
  • Prefer to use fmt.f() instead of string.format. For more info: fmt.f, Issue #1905.
  • Use scientific notation: "250e6" is a less error-prone way to write 250 million than "250000000". (Proper scientific notation would be "2.5e8", but in this author's opinion "250e6" is clearer.)

Libraries

Some Lua scripts, mainly those in dat/scripts, are meant to be reused.

The best practice (not always followed by older code) is for libraries to define a single table of functions and return it. Usage looks like local nice_lib = require "common.nice_lib" (in case of a library located at dat/scripts/common/nice_lib.lua). This form avoids namespace pollution: this statement doesn't define any variables except nice_lib.

Ideally, all library scripts should be documented. This is handled by LDoc, which is an improved LuaDoc but still a highly temperamental piece of software. To document dat/scripts/common/nice_lib.lua you would: add it to dat/scripts/meson.build, add a module description and @module directive to a block comment on top of the file, and add doc comments in front of functions.

LDoc is not Doxygen. There is no @brief directive. Parameters can be typed and/or optional. You can put an example function call after @usage. Unexpected breakage is the norm; it's a good idea to browse docs/lua/index.html in your build directory after a meson compile.

Combined example:

--[[--
   A nice library.
    
   @module nice_lib
--]]
local nice_lib = {}

--[[--
   Calculate the square of the input.

   @usage y = square(x)                

   @tparam[opt=0] x The number to square.
   @treturn number The input times itself.
--]]
function nice_lib.square( x )
   return (x or 0)^2
end 

return nice_lib

1: The meson build process generates HTML C and Lua docs in the docs subdir, as long as doxygen and ldoc are available. Lua API docs are mirrored here.