Coding Standards

Good code must be readable, maintainable, and make proper use of abstractions, in addition to behaving correctly. As such, we will be grading programming projects not just for functionality, but also for programming practices and style.

Python

We will adhere to the standard style guides for Python, specifically PEP 8 for code and PEP 257 for documentation. We recommend reading through both guides before writing any code.

Automated Style Checking for Python

Python has a number of automated tools for style checking. We will use the following tools:

As these tools are constantly evolving, we will use pinned versions so that you have a fixed set of targets to which you can write your code. The following will install these pinned versions in your Python environment:

$ wget https://eecs390.github.io/website/requirements.txt
$ pip3 install -r requirements.txt

You may need to run the second command with sudo.

For some projects, we will use these tools without any command-line options:

$ pycodestyle graph.py graph_test.py pagerank.py
$ pydocstyle graph.py graph_test.py pagerank.py
$ pylint graph.py graph_test.py pagerank.py

--------------------------------------------------------------------
Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)

For other projects, we will customize the behavior with command-line arguments. We will provide Makefile targets to run the tools with the appropriate arguments:

$ make style-pycode
pycodestyle ucbase.py ucexpr.py ucfunctions.py ucstmt.py uctypes.py
$ make style-pydoc
pydocstyle ucbase.py ucexpr.py ucfunctions.py ucstmt.py uctypes.py
$ make style-pylint
pylint --max-args=6 --max-module-lines=1500 ucbase.py ucexpr.py ucfunctions.py ucstmt.py uctypes.py

--------------------------------------------------------------------
Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)

$ make style
pycodestyle ucbase.py ucexpr.py ucfunctions.py ucstmt.py uctypes.py
pydocstyle ucbase.py ucexpr.py ucfunctions.py ucstmt.py uctypes.py
pylint --max-args=6 --max-module-lines=1500 ucbase.py ucexpr.py ucfunctions.py ucstmt.py uctypes.py

--------------------------------------------------------------------
Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)

You may not use any directives to disable the checks in these tools. Specifically, we prohibit the text pylint or noqa from appearing in any of the Python files you submit.

Advice

In some cases, the automated tools take positions that are stricter than those in PEP 8. We will discuss some of these situations here and provide advice on how to satisfy the tools.

Visual Conflicts with Multiline Conditionals

PEP 8 explicitly takes no position on indentation for multiline conditionals, where the second and subsequent lines are not visually distinguishable from the nested suite:

    if (prompt is not None and line != '' and
        not line.lstrip().startswith(comment)):
        print(prompt + line)

On the other hand, Pylint does not permit multiline conditionals to be indented as above. Instead, indent the conditional as one of the following:

    if (prompt is not None and line != '' and
            not line.lstrip().startswith(comment)):
        print(prompt + line)

    if (
            prompt is not None and line != '' and
            not line.lstrip().startswith(comment)
    ):
        print(prompt + line)

The second and subsequent lines of the test are indented a total of eight spaces beyond the level of the conditional. This avoids a visual confict with the indentation of the nested suite.

Unused Arguments

Pylint will flag function arguments that are unused in the body. In some cases, however, the argument is required to adhere to a specific interface, such as when overriding a method or using higher-order functions. In such a case, use the common Python convention of a single underscore to indicate that the variable is ignored:

    def map(self, _):
        """Map the given function across the items in this list."""
        return self

No Self Use

Pylint also flags base-class methods that do not use the self parameter. Such a method can be written as a static method instead:

    @staticmethod
    def is_procedure():
        """Return whether this represents a Scheme procedure.

        Returns true for both primitive and user-defined Scheme
        procedures.
        """
        return False

Unlike in other languages, static methods can be overridden, since Python always uses dynamic binding. A static method can even be overridden by an instance method, as long as the method is called with a receiver that is an instance and not a class itself.

Positional Arguments Appear to be Out of Order

If a function call’s arguments are identifiers that are exactly the same as the callee’s parameter names, but in a different order, Pylint warns that this may be erroneous. The following is an example:

def absdiff(arg1, arg2):
    """Return the absolute difference between arg1 and arg2."""
    return abs(diff(arg2, arg1))


def diff(arg1, arg2):
    """Return the difference between arg1 and arg2."""
    return arg1 - arg2

Here, the call to diff() passes the identifiers arg2 and arg1 in a different order than they appear in the parameter list of the definition of diff(). Since absdiff() is commutative, this happens to not be erroneous here, but there are other contexts in which such a mismatch is likely to be erroneous, motivating the Pylint warning.

There are several ways to work around this warning if the reordering is actually intended. One example is to introduce new variable names that are different than those in the callee:

def absdiff(arg1, arg2):
    """Return the absolute difference between arg1 and arg2."""
    value1, value2 = arg2, arg1
    return abs(diff(value1, value2))

Programming Practices

Some projects will also be hand-graded for programming practices such as making appropriate use of recursion and object-orientation and avoiding code duplication. The following are some of the pitfalls that we look for when hand grading:

Recursion

Recursive functions should avoid unnecessary code repetition and properly respect the abstraction barrier between an initial case and a recursive step. When work can be done by a recursive call, you should make the recursive call rather than repeating the computation in the caller.

Object Orientation

When writing object-oriented code, make appropriate use of inheritance and polymorphism to avoid code duplication and hard-coding types. Code that is shared among several classes should be written once in a base class and inherited. Derived-class methods that add functionality to that of the base-class method should make a call to the base-class method (using a mechanism such as super() in Python) rather than duplicating code. Method overriding should be used to customize behavior in a derived class, rather than type introspection (e.g. isinstance() in Python) followed by manual dispatch to custom code.