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:
- pycodestyle for PEP-8 compliance
- pydocstyle for PEP-257 compliance
- Pylint for static checking of Python code
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 (
is not None and line != '' and
prompt 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
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."""
= arg2, arg1
value1, value2 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:
- insufficient test cases that are distinct from the public tests
- significant code duplication
- non-descriptive variable, function, or class names
- avoiding the recursive leap of faith, leading to unnecessary cases in recursive functions
- overly nested code, where helper functions should be used instead
- use of
isinstance()
,@singledispatch
,match
, and similar patterns where method overriding can be used more cleanly (note that we do not prohibit all uses ofisinstance()
,@singledispatch
, ormatch
, just the ones that are unnecessary) - outdated comments or commented-out code, which make the code harder to read, understand, and maintain
- missing or uninformative documentation
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. Implicit dispatch-on-type via
@singledispatch
or match
should also be
avoided when method overriding can be used instead.