Jump to content

Guidelines:CodingStyle: Difference between revisions

From Sahana Eden Wiki
Dominic (talk | contribs)
Dominic (talk | contribs)
 
(11 intermediate revisions by the same user not shown)
Line 1: Line 1:
* [[Guidelines:Index|All Guidelines]]
* [[Guidelines:Index|All Guidelines]]


== Coding Style Guide ==
= General =


These conventions should be followed in all code.
The following conventions should be followed in all code.


This facilitates a team of developers working on the same code base. If each developer were to use their own style, there would be jarring changes in the code as someone read through it. This slows comprehension and can also cause spurious differences in changesets as developers alter style in frivolous ways on the code they touch.
This facilitates a team of developers working on the same code base. If each developer were to use their own style, there would be jarring changes in the code as someone read through it. This slows comprehension and can also cause spurious differences in changesets as developers alter style in frivolous ways on the code they touch.
Line 9: Line 9:
Our style guide aims to make the code base appear as if it was written by one precise punctilious programmer, rather than a a cacophony of competing coders. As with many aspects of coding and writing, there can be differences of opinion about the best style. These issues distract from the goal of writing code that works. Most project contributors have a slightly different personal coding style, but we all use the Sahana Eden style when contributing to the Sahana Eden project.
Our style guide aims to make the code base appear as if it was written by one precise punctilious programmer, rather than a a cacophony of competing coders. As with many aspects of coding and writing, there can be differences of opinion about the best style. These issues distract from the goal of writing code that works. Most project contributors have a slightly different personal coding style, but we all use the Sahana Eden style when contributing to the Sahana Eden project.


=== Quotes ===
= Python =
 
== Line-length and Indentation ==
 
Use '''four spaces''' for indentation.
 
Try to limit the length of code lines to '''80 characters''', where possible (PEP8). This is not a strict requirement, however:
* a string literal making the line longer is acceptable as long as it is the last code element in that line
* a line that is only slightly longer (up to 96 characters) is acceptable if it aids readability/comprehensibility
 
== Quotes ==


* for string literals in '''Python''' use double-quotes <code>" "</code>, unless the string contains double-quotes
* for string literals in '''Python''' use double-quotes <code>" "</code>, unless the string contains double-quotes
* for triple-quoted strings (such as ''Docstrings'' or ''Inline-XML'') use <code>""" """</code> with double-quotes, except:
* for triple-quoted strings (such as ''Docstrings'' or ''Inline-XML'') use <code>""" """</code> with double-quotes, except:
* for ''Inline-JavaScript'' or ''JSON'' inside '''Python''' code use <code><nowiki>''' '''</nowiki></code> with single-quotes
* for ''Inline-JavaScript'' or ''JSON'' inside '''Python''' code use <code><nowiki>''' '''</nowiki></code> with single-quotes


Note though that this:
Note though that this:


<code><nowiki>
<syntaxhighlight lang="python">
inline_javascript = '''sometext="%s";''' % sometext
inline_javascript = '''sometext="%s";''' % sometext
</nowiki></code>
</syntaxhighlight>


is a '''potential bug''' - the script breaks when <code>sometext</code> contains a <code>"</code>. A safer way to do that is:
is a '''potential bug and security risk''' - the script breaks when <code>sometext</code> contains a <code>"</code>. A safer way to do that is:


<code><nowiki>
<syntaxhighlight lang="python">
inline_javascript = '''sometext=%s;''' % json.dumps(sometext)
inline_javascript = '''sometext=%s;''' % json.dumps(sometext)
</nowiki></code>
</syntaxhighlight>


== Names ==


* for string literals in '''JavaScript''' use single-quotes <code>' '</code>, unless the string contains single-quotes
Names should be obvious and explicit, but not overly verbose (i.e. as long as they need to be to not make people think). They shouldn't require someone to look in another file or solve a puzzle to figure out what they mean, but shouldn't take too long to write. Avoid inventing new acronyms. e.g.


  bad: <code> prm_lkp</code>, <code>method_that_allows_us_to_create_a_gis_layer</code>


* for attributes in '''HTML''' (such as ''view templates'') use single-quotes <code>attr='value'</code>
* for attributes in other '''XML''' (e.g. ''XSLT'') use double-quotes <code>" "</code> (inner quotes must be escaped anyway)
=== Names ===
==== General ====
* Names should be obvious and explicit, but not overly verbose (i.e. as long as they need to be to not make people think). They shouldn't require someone to look in another file or solve a puzzle to figure out what they mean, but shouldn't take too long to write. Avoid inventing new acronyms. e.g.
  bad: <code> prm_lkp</code>, <code>method_that_allows_us_to_create_a_gis_layer</code>
   good: <code>personnel_search</code>, <code>create_gis_layer</code>
   good: <code>personnel_search</code>, <code>create_gis_layer</code>
==== Python ====


* Class names start with an uppercase letter.
* Class names start with an uppercase letter.
Line 52: Line 52:
* Variable names should not start with _undersore (a trailing underscore_ is acceptable in local scope, though)
* Variable names should not start with _undersore (a trailing underscore_ is acceptable in local scope, though)
* Indexes in names should be avoided (e.g. file1, file2), except where they designate new versions of classes/functions (e.g. AddPersonWidget2)
* Indexes in names should be avoided (e.g. file1, file2), except where they designate new versions of classes/functions (e.g. AddPersonWidget2)


* Names for Eden-specific subclasses of web2py classes and all classes which replace existing web2py classes should have the suffix "S3" (e.g. <code>AuthS3</code>)
* Names for Eden-specific subclasses of web2py classes and all classes which replace existing web2py classes should have the suffix "S3" (e.g. <code>AuthS3</code>)
Line 59: Line 58:
* Method names in Eden-specific classes do not need to (and thus should not) be prefixed
* Method names in Eden-specific classes do not need to (and thus should not) be prefixed
* avoid "s3" or "S3" prefixes in all other cases
* avoid "s3" or "S3" prefixes in all other cases


* <code>DataModel</code> subclasses in <code>s3db</code> should start with the all-uppercase module prefix, e.g. <code>DVRCaseModel</code>
* <code>DataModel</code> subclasses in <code>s3db</code> should start with the all-uppercase module prefix, e.g. <code>DVRCaseModel</code>
* <code>CRUDMethod</code> subclasses in <code>s3db</code> must start with the all-lowercase module prefix plus underscore, e.g. <code>pr_Contacts</code>
* <code>CRUDMethod</code> subclasses in <code>s3db</code> must start with the all-lowercase module prefix plus underscore, e.g. <code>pr_Contacts</code>


=== Docstrings ===
== Docstrings ==


All classes and functions must have docstrings that explain what the class or function is intended for and how it is to be used.  
All classes and functions must have docstrings that explain what the class or function is intended for and how it is to be used.  
Line 71: Line 69:


The function description in the docstring should be a statement in the third person about what the function does — not an imperative instruction telling it what to do.
The function description in the docstring should be a statement in the third person about what the function does — not an imperative instruction telling it what to do.
==== Python ====


We follow loosely the ''Google Style Guide'' for docstrings in Python, albeit with additional indentation:
We follow loosely the ''Google Style Guide'' for docstrings in Python, albeit with additional indentation:
Line 97: Line 93:
</syntaxhighlight>
</syntaxhighlight>


Type specifications for parameters and return value are optional, but strongly recommended where the types are not obvious.
Except where they are obvious, '''data types''' of parameters and return value '''should be included''' in the docstring.


In simple cases, ''PEP484-style type hints'' can also be acceptable - but are otherwise discouraged as they can easily impair comprehensibility, and not every syntax highlighter will aid the visual parsing of complex function annotations.
To that end, '''PEP484-style type hints''' can also be acceptable in simple cases - but are '''discouraged''' for more complex parameter lists as they can easily impair comprehensibility, and not every syntax highlighter will aid the visual parsing of function annotations.


<syntaxhighlight lang="python">
<syntaxhighlight lang="python">
Line 122: Line 118:
</syntaxhighlight>
</syntaxhighlight>


==== JavaScript ====
== Comments ==
 
Add comments so others can understand your code when they try to find bugs or improve/extend it. Comments in code should explain intent (or clarify techniques), not repeat the code (do not explain what is self-explaining).
 
Poor:
<syntaxhighlight lang="python">
# Set free to True
free = True
</syntaxhighlight>
 
Good:
<syntaxhighlight lang="python">
# Indicate that the position is now free
free = True
</syntaxhighlight>


In JavaScript, we use Java Docstrings.
Commented code should have a comment explaining why it is commented.
 
Unclear:
<syntaxhighlight lang="python">
#result = somefunction(somearg)
result = 0
</syntaxhighlight>
 
Better:
<syntaxhighlight lang="python">
# Not working yet, TODO: fix somefunction
#result = somefunction(somearg)
result = 0
</syntaxhighlight>
 
== Internationalisation ==
 
* All user-visible strings and labels ''must'' be internationalised: <code>T("My string")</code>
 
== Exception Handling ==
 
Catching exceptions too broadly can easily hide programming errors, obscure problem causes, and introduce serious risks for data integrity and security. Be as specific as possible as to which (type of) exception is to be caught, and always have a good reason for catching it.
 
Do not catch an exception ("Let it crash") unless:
* there is an immediate remedy or a meaningful alternative action, or
* the exception can be transformed into a meaningful error message that the users can act upon, or
* the exception needs to be logged (in which case it should be re-raised after logging)
 
All critical exceptions on the server-side should be logged.
 
Database exceptions should never be caught as they may block the current transaction and thus lead to another exception at a later point, obscuring the original cause of the problem.
 
== Print Function ==
 
For compatibility with WSGI as well as newer web2py (>=2.15), using the <code>print()</code> function in web application sources is '''strongly discouraged'''.
 
For everything that is to be executed in the restricted environment (i.e. all server code):
* for messages that are relevant during system setup (e.g. 1st run, module import failures etc.), use <code><nowiki>sys.stderr.write()</nowiki></code>
* for run-time debug/error messages: use <code><nowiki>current.log.*</nowiki></code>, as it can be controlled centrally, and routed to a log file when web2py isn't console-run
* for temporary debug output in code under development, use either <code><nowiki>sys.stderr.write()</nowiki></code> or <code><nowiki>print(..., file=sys.stderr)</nowiki></code>
 
 
For CLI scripts:
* use <code><nowiki>sys.stderr.write()</nowiki></code> for status/error messages, or <code><nowiki>sys.stdout.write()</nowiki></code> for results
* alternatively, use the <code><nowiki>print()</nowiki></code>-function
 
 
Using <code><nowiki>sys.std*.write()</nowiki></code> (remember that write expects string/buffer, so you must convert any parameters explicitly):
 
<syntaxhighlight lang="python">
import sys
 
# For status/error/debug messages:
sys.stderr.write("Here is a status message\n")
 
# For results:
sys.stdout.write("%s\n" % result)
</syntaxhighlight>
 
Using the logger:
<syntaxhighlight lang="python">
from gluon import current
 
# Use this for any permanent message output in server code:
current.log.error("Here is an error message")
 
current.log.debug("And this is a debug message")
</syntaxhighlight>
 
Using the print-function:
<syntaxhighlight lang="python">
import sys
 
# Using the print-function in CLI scripts:
print("The print-function", "accepts", "multiple arguments")
 
# Remember that server code must write to stderr, not stdout:
print("Direct output to stderr", file=sys.stderr)
</syntaxhighlight>
 
= JavaScript =
 
== Quotes ==
 
* for string literals in '''JavaScript''' use single-quotes <code>' '</code>, unless the string contains single-quotes
 
== Names ==
''tbw''
 
== Docstrings ==
 
In JavaScript, we use Java Docstrings. Type specifications for parameters and return values are mandatory.


<syntaxhighlight lang="JavaScript">
<syntaxhighlight lang="JavaScript">
Line 141: Line 242:
</syntaxhighlight>
</syntaxhighlight>


Type specifications for parameters and return values are mandatory here.
= HTML / XML =


=== Internationalisation ===
== Quotes ==


* All user-visible strings and labels ''must'' be internationalised: <code>T("My string")</code>
* for attributes in '''HTML''' (such as ''view templates'') use single-quotes <code>attr='value'</code>
 
* for attributes in other '''XML''' (e.g. ''XSLT'') use double-quotes <code>" "</code> (inner quotes must be escaped anyway)
=== Exceptions ===

Latest revision as of 12:58, 29 October 2026

General

The following conventions should be followed in all code.

This facilitates a team of developers working on the same code base. If each developer were to use their own style, there would be jarring changes in the code as someone read through it. This slows comprehension and can also cause spurious differences in changesets as developers alter style in frivolous ways on the code they touch.

Our style guide aims to make the code base appear as if it was written by one precise punctilious programmer, rather than a a cacophony of competing coders. As with many aspects of coding and writing, there can be differences of opinion about the best style. These issues distract from the goal of writing code that works. Most project contributors have a slightly different personal coding style, but we all use the Sahana Eden style when contributing to the Sahana Eden project.

Python

Line-length and Indentation

Use four spaces for indentation.

Try to limit the length of code lines to 80 characters, where possible (PEP8). This is not a strict requirement, however:

  • a string literal making the line longer is acceptable as long as it is the last code element in that line
  • a line that is only slightly longer (up to 96 characters) is acceptable if it aids readability/comprehensibility

Quotes

  • for string literals in Python use double-quotes " ", unless the string contains double-quotes
  • for triple-quoted strings (such as Docstrings or Inline-XML) use """ """ with double-quotes, except:
  • for Inline-JavaScript or JSON inside Python code use ''' ''' with single-quotes

Note though that this:

inline_javascript = '''sometext="%s";''' % sometext

is a potential bug and security risk - the script breaks when sometext contains a ". A safer way to do that is:

inline_javascript = '''sometext=%s;''' % json.dumps(sometext)

Names

Names should be obvious and explicit, but not overly verbose (i.e. as long as they need to be to not make people think). They shouldn't require someone to look in another file or solve a puzzle to figure out what they mean, but shouldn't take too long to write. Avoid inventing new acronyms. e.g.

 bad:  prm_lkp, method_that_allows_us_to_create_a_gis_layer
 good: personnel_search, create_gis_layer
  • Class names start with an uppercase letter.
  • CamelCase is for class names only.
  • Constant names are all-uppercase and with underscores as word separator.
  • everything else (including table names and field names) should be all-lowercase with underscores as word separator.
  • Names should not be Python keywords or standard library names
  • Variable names should not start with _undersore (a trailing underscore_ is acceptable in local scope, though)
  • Indexes in names should be avoided (e.g. file1, file2), except where they designate new versions of classes/functions (e.g. AddPersonWidget2)
  • Names for Eden-specific subclasses of web2py classes and all classes which replace existing web2py classes should have the suffix "S3" (e.g. AuthS3)
  • Names for Eden-specific methods (not overrides) in subclasses of web2py classes should start with the prefix "s3_" (e.g. s3_logged_in())
  • Eden-specific classes can have an "S3" prefix to their name in order to avoid name collisions, but ideally they should not (i.e. BasicCRUD rather than S3BasicCRUD)
  • Method names in Eden-specific classes do not need to (and thus should not) be prefixed
  • avoid "s3" or "S3" prefixes in all other cases
  • DataModel subclasses in s3db should start with the all-uppercase module prefix, e.g. DVRCaseModel
  • CRUDMethod subclasses in s3db must start with the all-lowercase module prefix plus underscore, e.g. pr_Contacts

Docstrings

All classes and functions must have docstrings that explain what the class or function is intended for and how it is to be used.

Exceptions can be made where the docstring would merely repeat documentation that already exists in another place, e.g. controllers, or standard interface methods inherited from (and thus documented in) a parent class.

The function description in the docstring should be a statement in the third person about what the function does — not an imperative instruction telling it what to do.

We follow loosely the Google Style Guide for docstrings in Python, albeit with additional indentation:

def card_details(r, idcard):
    """
        Displays the details of the new registration card, and offers
        a button to download the PDF.

        Args:
            (CRUDRequest) r: the request context
            (IDCard) idcard: the new registration card

        Returns:
            DIV: the card contents

        Raises:
            ValueError: if the idcard is invalid

        Note:
            Card secrets will not be rendered.
    """

Except where they are obvious, data types of parameters and return value should be included in the docstring.

To that end, PEP484-style type hints can also be acceptable in simple cases - but are discouraged for more complex parameter lists as they can easily impair comprehensibility, and not every syntax highlighter will aid the visual parsing of function annotations.

def card_details(r: CRUDRequest, idcard: IDCard) -> DIV:
    """
        Displays the details of the new registration card, and offers
        a button to download the PDF

        Args:
            r: the request context
            idcard: the new registration card

        Returns:
            the card contents

        Raises:
            ValueError: if the idcard is invalid or expired

        Note:
            Card secrets will not be rendered.
    """

Comments

Add comments so others can understand your code when they try to find bugs or improve/extend it. Comments in code should explain intent (or clarify techniques), not repeat the code (do not explain what is self-explaining).

Poor:

# Set free to True
free = True

Good:

# Indicate that the position is now free
free = True

Commented code should have a comment explaining why it is commented.

Unclear:

#result = somefunction(somearg)
result = 0

Better:

# Not working yet, TODO: fix somefunction
#result = somefunction(somearg)
result = 0

Internationalisation

  • All user-visible strings and labels must be internationalised: T("My string")

Exception Handling

Catching exceptions too broadly can easily hide programming errors, obscure problem causes, and introduce serious risks for data integrity and security. Be as specific as possible as to which (type of) exception is to be caught, and always have a good reason for catching it.

Do not catch an exception ("Let it crash") unless:

  • there is an immediate remedy or a meaningful alternative action, or
  • the exception can be transformed into a meaningful error message that the users can act upon, or
  • the exception needs to be logged (in which case it should be re-raised after logging)

All critical exceptions on the server-side should be logged.

Database exceptions should never be caught as they may block the current transaction and thus lead to another exception at a later point, obscuring the original cause of the problem.

For compatibility with WSGI as well as newer web2py (>=2.15), using the print() function in web application sources is strongly discouraged.

For everything that is to be executed in the restricted environment (i.e. all server code):

  • for messages that are relevant during system setup (e.g. 1st run, module import failures etc.), use sys.stderr.write()
  • for run-time debug/error messages: use current.log.*, as it can be controlled centrally, and routed to a log file when web2py isn't console-run
  • for temporary debug output in code under development, use either sys.stderr.write() or print(..., file=sys.stderr)


For CLI scripts:

  • use sys.stderr.write() for status/error messages, or sys.stdout.write() for results
  • alternatively, use the print()-function


Using sys.std*.write() (remember that write expects string/buffer, so you must convert any parameters explicitly):

import sys

# For status/error/debug messages:
sys.stderr.write("Here is a status message\n")

# For results:
sys.stdout.write("%s\n" % result)

Using the logger:

from gluon import current

# Use this for any permanent message output in server code:
current.log.error("Here is an error message")

current.log.debug("And this is a debug message")

Using the print-function:

import sys

# Using the print-function in CLI scripts:
print("The print-function", "accepts", "multiple arguments")

# Remember that server code must write to stderr, not stdout:
print("Direct output to stderr", file=sys.stderr)

JavaScript

Quotes

  • for string literals in JavaScript use single-quotes ' ', unless the string contains single-quotes

Names

tbw

Docstrings

In JavaScript, we use Java Docstrings. Type specifications for parameters and return values are mandatory.

/**
 * Retrieves events within a certain time interval.
 *
 * @param {moment|Date|string} start - the start of the interval
 * @param {moment|Date|string} end - the end of the interval
 *
 * @returns {Array} - the events within the interval,
 *                    or null if the interval is not completely cached
 */
EventCache.prototype.retrieve = function(start, end) {
    ...
};

HTML / XML

Quotes

  • for attributes in HTML (such as view templates) use single-quotes attr='value'
  • for attributes in other XML (e.g. XSLT) use double-quotes " " (inner quotes must be escaped anyway)