See www.zabbix.com for the official Zabbix site.
Docs/specs/coding style
Contents
|
General rules
- Use tabs only for indentation. Tab width depends on the language:
- Java, PHP, CSS, JavaScript - 4 characters
- C, autotools, m4, Perl, SQL - 8 characters
- There should be no trailing whitespace.
- Use LF line endings only.
- All text files should end with a single newline:
- C ISO standard requires one (http://c0x.coding-guidelines.com/5.1.1.2.html:
123 A source file that is not empty shall end in a new-line character, which shall not be immediately preceded by a backslash character before any such splicing takes place - PHP permits one (http://php.net/manual/en/language.basic-syntax.instruction-separation.php):
The closing tag for the block will include the immediately trailing newline if one is present - IEEE POSIX (http://www.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html):
3.205 Line: A sequence of zero or more non- <newline>s plus a terminating <newline>.
3.392 Text File: A file that contains characters organized into one or more lines. The lines do not contain NUL characters and none can exceed {LINE_MAX} bytes in length, including the <newline>
- C ISO standard requires one (http://c0x.coding-guidelines.com/5.1.1.2.html:
- Unused code should not be commented out, but removed.
- Comment text should be separated with a single space from the comment indicator.
- Non-obvious code should be commented. Also, as a general rule, if at least one developer was puzzled about something, it should be commented as well.
- All Zabbix developers are encouraged to read Deep C by Olve Maudal and Jon Jagger.
Database rules
Database table and field names use underscores between words. For example, expand_macros. While old table and field names lack underscores, they will not be mass changed, but new tables and fields, and ones that have to be modified for other reasons, should use underscores.
Language specific rules
shell
Shell scripts (like initscripts) and shell commands inside other files should follow these conventions:
- Shebang should always be added.
- Pipe character should have single space on each side if used for command chaining:
ifconfig eth0 | grep "inet addr"
- Stream redirection characters should have single space on each side, too:
create/schema/gen.pl c > src/libs/zbxdbhigh/dbschema.c
- Variables should be doublequoted always (except when that would change the intent).
- Suggested practices for target shell should be used - if writing for bash, $() instead of backticks, etc.
- If writing for sh, minimum subset of other utility functionality should be used (for example, sed -i should not be used as it might not be supported on some Unices).
- In variable comparisons, consider using xyes syntax. See Stackoverflow question on that.
- If you have to write to a file that other process might read and that might result in a race condition (where writing to the file has not been completed yet), use
mv, as it must be atomic according to POSIX. - If you have to create locking in a shell script, use directories, not files.
mkdiris atomic, thusmkdir lockdirshould never allow two processes to lock at the same time. - Don't use
catwhere it is not needed.grep string fileinstead ofcat file | grep string. Also see Useless use of cat. - Read BashFAQ.
SQL
SQL queries Zabbix components create/use must adhere to the following:
- SQL statements are always uppercase for the frontend code and database upgrade patches, and lowercase for server and proxy. This makes it easier to distinguish query source when debugging.
- No spaces are used around operators (comparison, mathematical, assignment etc) or commas that separate fields or values. This reduces amount of traffic to the SQL server.
- Only single spaces are used.
- Inside a query, only spaces are used, not tabs.
- For a multi-line query, spaces are always added at the beginning of a new line, not at the end of the previous one.
- For PHP, table aliases should always be used, regardless of the number of tables involved in a query. For server and proxy, they are only used if more than one table is involved in a query.
Specifically, in database patches:
- If a table alter query changes only one property, it takes one line. If it changes multiple properties, each property (including the first) gets it's own line.
For example:
Server SQL statements:
select hostid,host from hosts where hostid in (1,2,3,4,5,6) and status=3; update hosts set host='Zabbix server',ip='127.0.0.1',useip=1 where hostid=1014;
C conventions
Standards
- Even system and library functions that conform to POSIX.1-2001 cannot be used without checking (e.g, see ZBX-3937).
Formatting
- Lines should not be longer than 120 characters.
- Two or more consecutive empty lines should not be used.
- Variable declarations should be followed by an empty line to separate them from the rest of the code:
struct utsname name; if (-1 == uname(&name)) return SYSINFO_RET_FAIL;
- Code that sets up the context for if and while constructs should be separated from if and while by a blank line, unless the body of if and while consists of a single statement:
package = strtok(buf, "\n"); while (NULL != package) { ... package = strtok(NULL, "\n"); }
- goto labels should be written on a separate line, with no blank lines before and after:
res = SUCCEED; clean: zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __function_name, zbx_result_string(res));
- When type casting, no space should be added after the type:
*lastlogsize = (long)buf.st_size;
- Error messages should use "cannot" instead of "can't" or "could not":
zabbix_log(LOG_LEVEL_WARNING, "cannot remove shared memory for collector [%s]", strerror(errno));
- Hexadecimal constants should be written in lowercase:
#define MEM_MAX_SIZE 0x7fffffff- Variables and functions should be named with underscores between words:
int calculate_item_nextcheck(zbx_uint64_t interfaceid, zbx_uint64_t itemid, int item_type, int delay, const char *flex_intervals, time_t now, int *effective_delay);
- Use of camel case for naming variables in allowed only in Windows-specific code for consistency with Windows API:
PERFCOUNTER *counterName = NULL; DWORD dwSize;
- If a function is referenced in a comment, it should have parentheses after its name:
error = zbx_sock_last_error(); /* zabbix_log() resets the error code */
- Debugging information containing a single sentence should not be capitalized and should not end with a period:
zabbix_log(LOG_LEVEL_CRIT, "failed assumption about pointer size (%lu not in {4, 8})", ZBX_PTR_SIZE);
Comments
- Comments containing a single sentence should not be capitalized and should not end with a period:
/* check whether we need to update items_hk index */- Comments containing several sentences should be properly capitalized and end with a period:
/* The agent expects ALWAYS to have lastlogsize and mtime tags. Removing those would cause older agents to fail. */- Comment placement in regular code should indicate what the comment belongs to. For instance, a comment right before a single line of code or right before a block of consecutive lines indicates what this line or a block of lines does:
/* do not reallocate if not much is freed */ if (size > chunk_size / 4) return chunk;
- If a comment pertains to the whole block between { and }, it should be written on a separate line at the top of the block.
if (next_free) { /* merge with next chunk */ info->used_size -= chunk_size; info->used_size += size; ... }
- If there is a comment at the end of a line after a statement, it should be separated from the statement by tabs:
free_perf_collector(); /* cpu_collector must be freed before perf_collector is freed */
- In case of a multi-line comment treat every line as a single comment:
if (FAIL == result) { /* since we have successfully sent data earlier, we assume the other */ /* side is just too busy processing our data if there is no response */ ret = NETWORK_ERROR; }
- If there is a need for a comment before a function, but all six parts ("Function", "Purpose", "Parameters", "Return value", "Author", "Comments") would be too much, use only the "Comments" part. Use of single-line comments before function definitions should be avoided:
/****************************************************************************** * * * Comments: counter is NULL if it is not in the collector, * * do not call it for PERF_COUNTER_ACTIVE counters * * * ******************************************************************************/ PDH_STATUS zbx_PdhAddCounter(const char *function, PERF_COUNTERS *counter, PDH_HQUERY query, ...
Conditional statements
- If expression is compared to a constant, the constant should be written on the left:
if (SUCCEED == str_in_list("last,prev", function, ','))
- If a constant is used in a bitwise expression, it should be written on the right:
if (0x80 == (*text & 0xc0))
- If a numeric value or a pointer is tested for being non-zero, write 0 or NULL explicitly:
if (NULL != message_esc && 0 != message_esc_len)
- The only exception to the "constant on the left in comparison" rule is when a value is checked for being within certain bounds:
if ('1' <= *(br - 2) && *(br - 2) <= '9')
- If there is an aesthetic need for parentheses in a ternary expression, put them around the whole expression, not just the conditional part:
is_numeric |= (SUCCEED == _wis_uint(cpe->szCounterName) ? 0x02 : 0);
- In case conditional expression in an if takes several lines the logical operators should be written at the end of those lines and the following statement(s) should always be in braces. Also, second and further lines of the conditional expression should be indented with two tabs, not one:
if (0 == ioctl(s, SIOCGIFFLAGS, ifr) && 0 == (ifr->ifr_flags & IFF_LOOPBACK) && 0 == ioctl(s, SIOCGIFHWADDR, ifr)) { ... }
- If a conditional statement features if, else, and maybe even else if, and at least one of the branches contains multiple statements, there should be braces around each branch. The exception is the last else where braces can be omitted:
if (0 == found) { *curr = zbx_strpool_intern(new); } else if (0 != strcmp(*curr, new)) { zbx_strpool_release(*curr); *curr = zbx_strpool_intern(new); } else return FAIL;
- switch statement labels should be written on a separate line, too:
switch (dcheck->type) { case SVC_TELNET: service = "telnet"; break; case SVC_ICMPPING: break; default: return FAIL; }
Macros
- In macro definitions, #define should be followed by a space and the macro name should be followed by a tab:
#define ZBX_PROCESS_SERVER 0x01- In macro conditionals, #ifdef should be followed by a space:
#ifdef HAVE_LDAP- In nested macro conditionals, # character should remain the first character on the line, the rest should be separated by tabs:
#ifdef HAVE_LIBCURL # include <curl/curl.h> # if !defined(HAVE_FUNCTION_CURL_EASY_ESCAPE) # define curl_easy_escape(handle, string, length) curl_escape(string, length) # endif #endif
- Conditional compilation should not affect indentation of regular code:
#ifndef _WINDOWS int get_cpustat(AGENT_RESULT *result, int cpu_num, int state, int mode); #endif
- Comments in macro conditionals (e.g. after #else or #endif) are only encouraged if it is not clear what the conditional refers to otherwise:
#ifdef _WINDOWS # include "perfmon.h" # include "perfstat.h" #endif
- Use #ifdef only for simple conditions (i.e. without #elif statements). In other cases, always use the full #if defined(MACRO) or #if !defined(MACRO) form (note: #ifndef should not be used):
#ifdef _WINDOWS if (WSAEAFNOSUPPORT == zbx_sock_last_error()) #else if (EAFNOSUPPORT == zbx_sock_last_error()) #endif
- Comments after #else should always describe the following block:
#ifdef _WINDOWS ... #else /* not _WINDOWS */ ... #endif
Compiler-specific issues
This section describes why we do not use full features of our programming language and current best practices because of incompatibility with older compilers and software.
- Do not use %zu directive for outputting variables of type size_t. Use macro ZBX_FS_SIZE_T as the format specifier and macro zbx_fs_size_t as the type to convert to in calls to printf():
zabbix_log(LOG_LEVEL_DEBUG, "In %s() datalen:" ZBX_FS_SIZE_T, __function_name, (zbx_fs_size_t)j->buffer_size);
- Older versions of glibc do not support z modifier:
zabbix_log(LOG_LEVEL_DEBUG, "In %s() datalen:%zu", __function_name, j->buffer_size);
- When writing preprocessor directives, leave the # character is the first column.
#ifdef HAVE_LIBCURL # include <curl/curl.h> # if !defined(HAVE_FUNCTION_CURL_EASY_ESCAPE) # define curl_easy_escape(handle, string, length) curl_escape(string, length) # endif #endif
- Some compilers do not recognize the following code as valid:
#ifdef HAVE_LIBCURL #include <curl/curl.h> #if !defined(HAVE_FUNCTION_CURL_EASY_ESCAPE) #define curl_easy_escape(handle, string, length) curl_escape(string, length) #endif #endif
- When writing conditionally compiled code, avoid splitting if conditional into several parts:
#ifdef _WINDOWS if (PF_INET6 == current_ai->ai_family && ZBX_TCP_ERROR == setsockopt(s->sockets[s->num_socks], IPPROTO_IPV6, IPV6_V6ONLY, (void *)&on, sizeof(on))) #else if (PF_INET6 == current_ai->ai_family && ZBX_TCP_ERROR == setsockopt(s->sockets[s->num_socks], SOL_IPV6, IPV6_V6ONLY, (void *)&on, sizeof(on))) #endif
- Some compilers do not recognize the following code as valid:
if (PF_INET6 == current_ai->ai_family && #ifdef _WINDOWS ZBX_TCP_ERROR == setsockopt(s->sockets[s->num_socks], IPPROTO_IPV6, IPV6_V6ONLY, (void *)&on, sizeof(on))) #else ZBX_TCP_ERROR == setsockopt(s->sockets[s->num_socks], SOL_IPV6, IPV6_V6ONLY, (void *)&on, sizeof(on))) #endif
- Avoid using complicated expressions (more complicated than simple assignment of a single value) in variable declarations. For instance, the following code is good:
char *p_dst = NULL;
- Some compilers have trouble compiling more complicated expressions like the following:
size_t src_size = strlen(src); int feeds = src_size / maxline - (0 != src_size % maxline ? 0 : 1);
PHP conventions
- Single quotes should be used to define strings.
- If function requires array or object as a parameter, that parameter should be type hinted.
public function do(array arrayParameter, classNameOrInterface classParameter) {}
- Global variables except of PHP predefined ($_GET, $_POST, $_SERVER, ...) must not be used.
- To check an expression against null, the identical operator should be used instead of the is_null() function.
if ($var === null) {}
- .htaccess files only define settings for the current directory. This is done to avoid the possibility of missing a change for this file if some subdirectory is renamed.
Input field limits
All input fields in the frontend are limited according to what the receiving database field length limit is. API does input length validation the same way.
For example :
- IP input fields are limited to 39 characters (to accommodate IPv6 addresses and user macros).
- DNS input fields are limited to 64 characters.
- Port input fields are limited to 64 characters (to accommodate user macros).
Naming conventions
- All names should be written in English.
- Variable names must be in mixed case starting with lower case:
line, audioSystem- Names representing constants (final variables) must be all uppercase using underscore to separate words:
MAX_ITERATIONS, COLOR_RED- Names representing methods must be verbs and written in mixed case starting with lower case:
getName(), computeTotalWidth()
- Abbreviations and acronyms should not be uppercase when used as name:
exportHtmlSource(); // NOT: exportHTMLSource(); openDvdPlayer(); // NOT: openDVDPlayer();
- Private class variables should have underscore prefix:
class Person { private _name; ... }
- License comments should not be minified ( comment with /*! )
Class naming
All class names must be in UpperCamelCase. If class extends another class, child class name should be prefixed to parent class name. Example:
class Writer {} class XmlWriter extends Writer {} class FastXmlWriter extends XmlWriter {}
Class files naming
Files that contain class should be named as class name with ".php" extension. Example:
class Writer {} -> Writer.php
class XmlWriter {} -> XmlWriter.php
Formatting
In PHP files only one opening "<?php" tag must exists at the first line of file. Closing "?>" should be omitted.
If a concatenation with . (a dot) spans multiple lines, the dot is at the end of the previous line (not in the beginning of the next one).
Blank spaces
- A blank space should appear after commas in argument lists.
- All binary operators except . (a dot) should be separated from their operands by spaces. Blank spaces should never separate unary operators such as unary minus, increment ("++"), and decrement ("--") from their operands.
- A keyword followed by a parenthesis should be separated by a space.
- When concatenating strings in separate rows, space (if required) is added at the beginning of the second row.
Example
<?php class Example { public function ifExample($a, $b) { if (convert($a) > $b) { echo "a is bigger than b"; } elseif ($a == $b) { echo $a." is equal to ".$b[0]; } else { echo $this->property; } $result = ($a < $b) ? $a : $b; } public function forExample() { for ($i = 1; $i <= 10; $i++) { echo 'Item: '; echo $i; } } public function foreachEample() { $arr = array(1, 2, 3, 4, "b" => 5, "a" => 6); foreach ($arr as &$value) { $value = (int) $value * 2; } } public function whileExample() { $i = 1; while ($i <= 10) { echo $i++; } } public function doWhileExample($i) { do { echo $i--; } while ($i > 0); } public function switchExample($i) { switch ($i) { case 0: echo "i equals 0"; break; case 1: echo "i equals 1"; break; default: throw new Exception('Wrong argument'); break; } } public function tryExample() { try { echo inverse(5)."\n"; } catch (Exception $e) { echo 'Caught exception: '.$e->getMessage()."\n"; } } } ?>
Blank lines
- Blank line should appear before block comments.
- One blank line after class declaration.
- One blank line after method definition in class.
PHPDoc
All functions, methods and properties should have a PHPDoc comment with a brief description of it's purpose and interface. Tag descriptions should be aligned using spaces and tags belonging to one group should be separated by a single line to improve readability.
Example
<?php /** * Performs a cool magic trick. * * @throws Exception if something goes wrong. * * @param FairyDust $fairyDust The FairyDust object to use in the trick * @param array $magicStones An array of magic stones * * @return mixed */ function performMagic(FairyDust $fairyDust, array $magicStones) { }
Line length
Maximum line length is 120 characters.
Statement formatting
Statement arguments should not be included in parenthesis. See also PEAR standard.
Example:
require_once dirname(__FILE__).'/include/hosts.inc.php';
Compare with null
For comparison with null "===" operator is used, not is_null() function.
Multiline function parameters and array elements
For function parameters and arrays definitions, there is no trailing comma after the last element both for definitions on single and multiple lines. If a function parameter or array element definition spans multiple lines, closing parenthesis should be on a separate line.
Example:
$data = array( 'username' => $username, 'password' => $password, 'email' => $email );
Function parameters
Formatting
If preprocessing is not required for function parameters, it is passed directly to function without creation of separate variable.
Examples:
DBselect('SELECT i.itemid FROM items'); API::Item->get(array( 'itemids' => array(123), 'preservekeys' => true, 'editable' => true ));
Modifying parameters
If the function receives a parameter and modifies it, it should be returned, not passed by reference. This should make the code easier to understand and prevent situations, when a function modifies the array unexpectedly.
Examples:
// this is correct function modifyValues(array $values) { // do the magic // return the modified values return $values; } // this is wrong! function modifyValues(array &$values) { // do the magic }
File includes
For file include require_once is used. require_once raises a fatal error if the file is not found. All of the include paths should be relative to the directory of the file, that includes the script.
Example:
// correct require_once dirname(__FILE__).'/include/hosts.inc.php'; // wrong require_once 'include/hosts.inc.php';
Directory hierarchy
All classes should be placed under classes directory. If there is bunch of classes that are related to one functionality, they should be placed in a sub-directory. Example:
classes/export/Export.php classes/export/writers/Writer.php classes/export/writers/XmlWriter.php classes/export/writers/JsonWriter.php classes/export/exportelements/ExportElement.php classes/export/exportelements/HostExportElement.php classes/export/exportelements/ItemExportElement.php
HTML encoding and JavaScript escaping
- All unsafe data in both HTML and JavaScript must be sanitized before outputting to avoid syntax errors and XSS attacks.
- HTML tag and attribute contents should be escaped using the CHtml::encode() method.
Example:
// note, that only the GET parameter should be encoded, not the whole string $span = new CSpan('Hello, '.CHtml::encode($_GET['name'])); echo $span;
Plain HTML example:
<span>Hello, <?php echo CHtml::encode($_GET['name']) ?></span>
- Parameters inserted into JavaScript code should be encoded into JSON using the CJs::encodeJson() method.
Example:
console.log(<?php echo CJs::encodeJson($_GET['name']) ?>); console.log(<?php echo CJs::encodeJson(array('name' => $_GET['name'], 'key' => $_GET['item'])) ?>); // if the value will be used in HTML code, it should additionally be HTML encoded jQuery('#name').html(<?php echo CJs::encodeJson(CHtml::encode($_GET['name'])) ?>);
- When passing an array to JavaScript via the data-* attributes, it can be safely serialized with the CHtml::serialize() method.
Example:
<a href="#" id="item" data-params="<?php echo CHtml::serialize(array('name' => $_GET['name'], 'key' => $_GET['item'])) ?>">My Item</a>
// the data() method will automatically handle JSON decoding and return an object jQuery('#item').data('params');
JavaScript conventions
- Using global variables should be avoided as much as possible.
- "use strict"; must be used.
- Constructor functions should be named in UpperCamelCase.
Naming conventions
- All names should be written in English.
- Variable and function (except constructors) names must be in lower camel case.
- Constructor function names must be in upper camel case.
Blank spaces
- A blank space should appear after commas in argument lists.
- All binary operators should be separated from their operands by spaces. Blank spaces should never separate unary operators such as unary minus, increment ("++"), and decrement ("--") from their operands.
- A keyword followed by a parenthesis should be separated by a space.
DOM traversing
DOM traversing should be done in way, that avoids being limited to a specific DOM structure, e.g using $.closest() instead of $.parent(), $.find() instead of $.children() and using class names and IDs in selectors.
<ul id="list"> <li class="item"> <span> <a href="#" class="remove">Remove item</a> </span> </li> </ul> <script> // correct jQuery('#list').on('click', '#list .item .remove', function() { jQuery(this).closest('.item').remove(); }); // wrong jQuery('#list').on('click', '#list li span a', function() { jQuery(this).parent().parent().remove(); }); </script>
HTML conventions
Naming conventions
- Class names, IDs and data attributes should be written with a dash, e.g. .item-form or data-item-id.
- Class names should be chosen based on their semantic values, not on their appearance or a particular location on the layout, e.g. .error instead of .red, or .main-menu. instead of .top-menu.
Tag usage
- HTML tags should used based on their semantic values, not on their visual appearance, e.g. strong may be used to highlight something important, but not just to make the text bold. Tags, that have no semantic value, such as font or br, should not be used at all.
- The visual appearance of the elements should not be defined using HTML tags, such as font or br, but CSS.
CSS conventions
Selectors
- CSS selectors for structured groups of elements should start with the root element.
<div class="widget"> <div class="header"></div> <div class="body"></div> </div>
/* correct */ .widget { ... } .widget .header { ... } .widget .body a { ... } /* wrong */ .widget { ... } .header { ... } .body a { ... } /* also wrong */ .widget { ... } .widget-header { ... } .widget-body a { ... }
- Selectors for common inline elements can be written without specifying their location.
.button { ... } a:visited { ... }
- Styles, that are specific for some page, should be defined starting from the unique page root element.
/* some special styles for a widget on the trigger monitoring page */ .w.list.mon-trigger .widget .body { ... } /* different .button styles for forms */ .w.edit .button { ... }
- Avoid using unnecessary HTML elements in selectors to be less bound to a specific DOM structure.
/* correct */ .w.list .item .status strong { ... } /* wrong */ .w.list tr.item td.status strong { ... }
Style definition
- Styles should always be defined in the corresponding CSS files, never in the style attribute.
- Common styles should be put in default.css, theme specific styles - in the corresponding theme's CSS file (the only exception is if we need to set some specific user-defined value, e.g. a path to particular background image, that is stored in the database).
- Styles should be defined starting from more common to more specific.
/* some common input and button styles */ input { ... } .button { ... } /* input and button styles inside of object configuration forms */ .w.edit input { ... } .w.edit .button { ... } /* specific styles for the item configuration form */ .w.edit.cfg-item input { ... } .w.edit.cfg-item .button { ... }