diff --git a/CodingStandards.md b/CodingStandards.md index 3b28492..be23eb9 100644 --- a/CodingStandards.md +++ b/CodingStandards.md @@ -1,24 +1,24 @@ -# coding standards +# Coding standards Here are some Python code style guidelines. We also include official Python guidelines by reference (see the section on "official Python standards" below). This document overrides the official standards whenever there is a conflict. -## basic standards -### compatibility +## Basic standards +### Compatibility Tahoe requires Python v2.6.6 or greater (although the current code only refuses to run on Python < 2.6). No effort should be made to offer compatibility with versions of Python older than 2.6.6. Effort should be made to work with every Python release from v2.6.6 to the most recent 2.x, inclusive. -### naming and layout +### Naming and Layout * Use `underscore_separated_names` for functions, `CamelCapNames` for classes, `alllowercasenames` for modules, and `ALL_CAPS_NAMES` for constants. Use all lower-case variable names (e.g. `variable_name` or `variablename`). Prepend a leading underscore to private names. * Put parentheses around tuples if it helps make the code more readable, leave them off if not. -### comments, idioms, miscellany, license, imports, docstrings, line widths +### Comments, Idioms, Miscellany, License, Imports, Docstrings, Line widths Here is a useful header for starting new Python files: ``` """ -optional doc string describing the module here +Optional doc string describing the module here. """ # import Python Standard Library modules here @@ -38,18 +38,16 @@ from allmydata.util.assertutil import _assert, precondition, postcondition * Ignore the part of PEP-8 which specifes 79- or 72- char line widths. Lines should preferably be less than 100 columns, but we don't enforce this strictly. It is more important to break lines at points that are natural for readability than to follow a fixed line width restriction. Where possible, continuation lines should be indented as far as necessary to make them match up with the subexpression (e.g. argument list) they belong to. * PEP 8 says: "If operators with different priorities are used, consider adding whitespace around the operators with the lowest priority(ies)." This is a good rule; note that it also applies to some non-obvious low-priority operators, like '`:`' for list slicing. (Example: `a[: d]b-c` good, `a[- c:d]b` bad. If a slice is from the start or to the end of the array, put the '`:`' immediately next to the bracket on that side.) -### truths and falsehoods +### Truths and Falsehoods * Don't use the literals `True` or `False` in conditional expressions -- instead just write the expression which will evaluate to true or false. For example, write `if expr:` instead of `if expr == True:` and `if not expr:` instead of `if expr == False:`. * Avoid relying on the fact that empty sequences, empty strings, empty dicts, `0`, and `None` are treated as false. Write `if len(items) == 0:`, `if thing is None:`, etc. -## advanced idioms +## Idioms +### Preconditions and Assertions - -### preconditions and assertions - -#### basic preconditions and assertions +#### Basic preconditions and assertions Make sure you have `from allmydata.util.assertutil import _assert, precondition, postcondition` in your imports (as shown in the template above). Now design preconditions for your methods and functions, and assert them like this: @@ -71,15 +69,15 @@ Traceback (most recent call last): AssertionError: precondition: emLen is required to be big enough. -- emLen: 20 , 'SIZE_OF_UNIQS': 20 ``` -The "error message" that will accompany a failed expression should be a statement of what is required for correct operation. Don't write something like "Spam isn't firm.", because that is ambiguous: the error could be that the spam is supposed to be firm and it isn't, or the error could be that spam isn't supposed to be firm and it is! The same ambiguity can apply to the sentence "Spam must be firm.". It helps to use the words "required to" in your message, for example "Spam is required to be firm.". +The "error message" that will accompany a failed expression should be a statement of what is required for correct operation. Don't write something like "Spam isn't firm.", because that is ambiguous: the error could be that the spam is supposed to be firm and it isn't, or the error could be that spam isn't supposed to be firm and it is! It helps to use the words "required to" in your message, for example "Spam is required to be firm.". Assertions are not a substitute for proper error handling! An assertion, precondition or postcondition should only be used for cases that "cannot happen" unless the code is incorrect. They should not be used to check for invalid inputs; in that case, raise an exception instead. -==== avoid "bare assert" ==== +==== Avoid "bare assert" ==== Python's built-in `assert` statement, unlike `allmydata.util.assertutil._assert`, can be switched off by the `-O` option to `python` or the `PYTHONOPTIMIZE` environment variable. Although this might sound useful to reduce the overhead of assertions, in practice that overhead is negligable, and conditional assertions are more trouble than they're worth (partly because they create a configuration that is mostly untested). We are in the process of removing all bare asserts from the codebase (#1968). -==== class invariants ==== +==== Class invariants ==== If your class has internal state which is complicated enough that a bug in the class's implementation could lead to garbled internal state, then you should have a class invariant. A class invariant is a method like this (an actual example from !BlockWrangler, but truncated for space): @@ -99,9 +97,9 @@ def _assert_invariants(self): Now you can put `assert self._assert_invariants()` everywhere in your class where the class ought to be in an internally consistent state. For example, at the beginning of every externally-callable method. This technique can be very valuable in developing a complex class -- it catches bugs early, it isolates bugs into specific code paths, and it clarifies the internal structure of the class so that other developers can hack on it without subtle misunderstandings. -* we actually appear to only have one instance of this pattern in Tahoe at time of writing, in `allmydata.util.dictutil`. It has the disadvantage of cluttering up the logic with calls to `_assert_invariants`, and should probably be used sparingly. -- Daira +* We actually appear to only have one instance of this pattern in Tahoe at time of writing, in `allmydata.util.dictutil`. It has the disadvantage of cluttering up the logic with calls to `_assert_invariants`, and should probably be used sparingly. -- Daira -==== assertion policy ==== +==== Assertion policy ==== One axis of interest is how time-consuming the checks are. Many precondition checks can cause typical runtime to explode to O(n^2^) or O(n^3^), for example @@ -133,15 +131,15 @@ callers. checks. -=== configuration === +=== Configuration === -==== minimizing configuration ==== +==== Minimizing configuration ==== * Do not implement configuration files for modules or libraries -- code that is going to be used by other code. Only applications -- code that is going to be used by humans -- have configuration files. Modules and libraries get "configured" by the code that calls them, for example by passing arguments to their constructors. * If there are constant values which end-users do not need to modify, then do not make them configurable, but put them in all-caps variables at the beginning of the Python file in which they are used. * Design algorithms so that they have as few "voodoo constants" and "tweakable parameters" as possible. -==== how to implement configuration ==== +==== How to implement configuration ==== Whether in application code or in library code, never pass configuration values via a configuration object. Instead use Python parameters. For example -- here's another real-life example -- do not write @@ -169,7 +167,7 @@ class BlockStore: . -## official Python standards +## Official Python Standards These are listed in decreasing order of priority, so if a point in one of the latter guidelines contradicts a point in one of the earlier ones, then go with the earlier. The Tahoe-LAFS-specific guidelines above override all else, of course.