Permalink
Branch: master
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
225 lines (155 sloc) 8.92 KB

Style Guide

Rationale

To keep the codebase maintainable and readable, all code is developed using a similar coding style. It ensures:

  • the code is easy to maintain and understand.
    • It's really important that the original developer able to come back to code 5 months later and still quickly understand what it's supposed to be doing.
    • For other people that want to contribute it is necessary for them to quickly understand the code.
  • that every developer knows to (largely) expect the same coding style.

Having a unified style makes it much easier to maintain the our projects. That means that every developer should be able to make changes in any file in the log2timeline organization without worrying about different code styles.

Project-specific deviations

Some log2timeline projects systematically vary from this guide. Specifically:

Basis

Log2Timeline's style is based on the the Google Python Style Guide, with specific variations and expansion spelled out in the sections below.

Compatibility

All new log2timeline code should be compatible with both Python 3.4+ and Python 2.7+.

Indentation

  • Indent your code blocks with 2 spaces (not 4 as in the Google Python Style Guide).
  • In the case of a hanging indent, use four spaces.

Naming

  • Use full English words everywhere. For example, use Event not Evt and Description not Desc.Python-3-Guide.md
  • Acronyms and initialisms should be preserved, such as HTMLParser and not HtmlParser.
  • Use "cls" as the name of the class variable in preference to "klass"
  • Method and function names follow the following logic (overriding the Google Python Style Guide):
Type Public Internal
Functions CapWords() _CapWords() (protected) and __CapWords() (private)

Linting

log2timeline uses pylint 1.7.x to enforce some additional best practices to keep the source code more readable. These are:

  • Limit the maximum number of arguments for function or method to 10

Other things to keep in mind when using Pylint:

  • Use textual pylint overrides e.g. "# pylint: disable=no-self-argument" instead of "# pylint: disable=E0213". For a list of overrides see: http://docs.pylint.org/features.html
  • The canonical pylint configuration for log2timeline project is generated by l2tdevtools.

Function and method arguments

  • In addition to the Google Python Style Guide sort keyword arguments alphabetically by name.
  • Call keyword argument with their keyword prefix, not as positional arguments. For example:
def SomeMethod(self, alternate=False, secondary=True):
  pass

# No:
SomeMethod(True, False)

# Yes:
SomeMethod(secondary=False)

Strings

  • Quote strings as ' (one single quote) or """ (three double quotes) and not " (one double quote).
    • Quote strings in command line arguments (argparse) as "
  • Textual strings should be Unicode strings.
  • Use the format() function instead of the %-style of formatting strings.
    • Use positional or parameter format specifiers with typing e.g. '{0:s}' or '{text:s}' instead of '{0}', '{}' or '{:s}'. If we ever want to have language specific output strings we don't need to change the entire codebase (again). It also makes is easier in determining what type every parameter is expected to be.

Exceptions

  • When catching exceptions use "as exception:" not some alternative form like "as error:" or "as details:"
  • Raise exceptions like this: raise MyException('Error message') or raise MyException.
  • Although Python allows for try ... except ... else we prefer not to use it.
  • Make exception messages as useful and descriptive and possible. For example, if an argument is out of an acceptable range, print the invalid value to speed-up debugging.

Return statements

Per PEP8: "Be consistent in return statements. Either all return statements in a function should return an expression, or none of them should. If any return statement returns an expression, any return statements where no value is returned should explicitly state this as return None, and an explicit return statement should be present at the end of the function (if reachable)."

  • Use return None instead of return when your function or method is expected to return a value.
  • Do not use return None in generators.
  • Use return in a function or method that does not return a value.

Docstrings

  • Use English, and avoid abbreviations. Use "for example" or "such as" instead of Latin abbreviations like "e.g.".
    • Acronyms and initialisms should be preserved, for example HTMLParser and not HtmlParser.
  • We use "Google Style" docstrings.
    • For examples, see this page as well as the notes below.

Example:

def AddAnalysisReport(self, analysis_report, storage_writer=None):
    """Adds an analysis report.
    
    Args:
      analysis_report (AnalysisReport): a report.
      storage_writer (Optional[StorageWriter]): the storage writer must be open, 
          and must not be closed. If no storage_writer is provided, a new writer 
          will be created.
    """

Make sure your arguments descriptions include:

  1. The argument(s) type(s);
  2. In case of standard types a description of their format. Note that we use the Python 3 standard types;
  3. Description of the meaning of the argument. In other words how the argument is used by the function (or method). If the description exceeds the line limit, indent the next line with 4 spaces.

The meaning can be left out if the function has only a couple arguments and how the arguments are used is obvious from the description as in the example of AddAnalysisReport.

A few other tips:

Compound types

If a function deals with a compound type (list, dict), document it like so:

Args:
  constraints (dict[str, Filter]): constraint name mapped to the filter that implements the constraint.

Returns:
  list[BaseParser]: all relevant parsers.

Multiple acceptable types

If you need to specify multiple types, use a pipe to separate them. For example:

Args:
  path (str|Path): path to tag file.

Multiple return types

Python simulates multiple arguments being returned by implicitly returning a tuple. Document like so:

...
Returns:
  tuple: containing:
     
    str: parser name
    BaseParser: next parser parser
""""
return name, parser

Special arguments

Arguments like cls, self, *args, **kwargs should not be explicitly named in the Args: section.

  def CopyToIsoFormat(cls, timestamp, timezone=pytz.UTC, raise_error=False):
    """Copies the timestamp to an ISO 8601 formatted string.

    Args:
      timestamp (int): number of micro seconds since January 1, 1970, 00:00:00 UTC.
      timezone (Optional[pytz.timezone]): the result string will be expressed in this timezone.
      raise_error (Optional[bool]): False if OverflowError should be caught when timestamp is out of bounds.

    Returns:
      str: ISO 8601 formatted date and time.
    """

Class attributes

In addition to the Google Python Style Guide sort class attribute alphabetically by name.

class SampleClass(object):
  """Summary of class here.

  Attributes:
    eggs (int): number of eggs we have laid.
    likes_spam (bool): whether we like SPAM or not.
  """

Constructors

In addition to the Google Python Style Guide sort instance attribute alphabetically by name inside the constructor (__init__).

class SampleClass(object):
  """Summary of class here."""

  def __init__(self):
    """Summary of method here."""
    super(SampleClass, self).__init__()
    self._protected_attribute = None
    self.another_public_attribute = None
    self.public_attribute = None

Unit tests

  • Use self.assertEqual instead of self.assertEquals, same applies to self.assertNotEquals
  • Use self.assertIsNone(variable) instead of self.assertEqual(variable, None), same applies to self.assertNotEqual

Other

  • Avoid the use of global variables.
  • Use class methods in preference to static methods
  • At the top of source files define the encoding, which should be UTF-8, e.g.:
# -*- coding: utf-8 -*-

Also see: PEP 0263