Coding standards
Backend code standards
Python conventions
- Newer projects use black for Python code formatting. We follow the basic config, without any modifications. It should be required for new projects and is warmly welcome for the old ones.
- We also use isort for keeping all the imports nice and clean. To make it compatible with black add the following line either to your project's setup.cfg file's
[isort]
section or to isort.cfg:
bash multi_line_output=3include_trailing_comma=Trueforce_grid_wrap=0use_parentheses=Trueline_length=88
- Ensure that PEP-8 standards are upheld with the help of flake8.
Write Pythonic code, not Java (< 5) with pep8
Not
colors = ['red', 'green', 'blue']for i in range(len(colors)):print(colors[i])but
for color in colors:print(color)(CC-BY-SA Raymond Hettinger)
Make pytest tests, and run tests against latest released Python 3. Run tests with deprecation warnings on.
Use pyenv to install up to date (or older!) Python versions not available in your distribution repos
Use
pyenv virtualenv
(orpython -m venv
if not using pyenv) for creating virtualenvs, notvirtualenv
or other alternatives when using virtual environments in scripts.Do not use print unless you really specifically want to print to stdout (for example with a cli tool). Write status, error, debug etc. messages using the logging module. This allows a main program using the code from elsewhere to configure the logging target (to stderr, file, syslog, or logging server). Do not use the warnings module.
Prepare the project for distribution in the same way as the pypa sampleproject: Write a setup.py, setup.cfg, README.md, LICENSE (in most cases the MIT license) and .travis.yml files, create a directory for tests (named
tests
), but make a top level directories for the actual package code namedsrc
, not the same as the package. In case the project is not a reusable part of a bigger application, but a standalone app requiring a more complex installation (like a Django project requiring a database setup etc.), you can skip the setup.py setup.cfg.Use static typing for functions instead of comments. That way IDEs can give better info, and we can do some static checking (so the type info will not get stale, unlike comments).
Not like this:
def func_with_docstring(foo, bar):"""Return the baz of foo and barArgs:foo: intbar: floatReturns:float"""return bar/fooBut like this:
def func_with_typing(foo: int, bar: float) -> float:"""Return the baz of foo and bar"""return bar/fooUse mypy for checking
Use your editor tooling (for example PyCharm can benefit from types)
Check PEP 3107 (function annotations), PEP 484 (defined format for annotations + comment annotations) and PEP 526 (syntax for variable annotations)
Use a doctest to illustrate use and also run the doctests (again, so that they won't get stale).
Use the secrets module for cryptographic needs, not the
random
moduleUse whitelists for reading
POST
data to avoid mass assignment problemsDo not use truthiness to check for existence, since values like None, False, 0, and '' will fail the check.
- Use
is not None
ornot in iterable_instance
and so on, as appropriate
- Use
Do not use assert for normal runtime checks (for example to validate incoming data) as they will be disabled with debug off (in cpython). Use plain ifs and then raise assertions explicitly
- Testing uses the assert as syntax sugar, which is fine. You don't run tests in production.
Do not use tuples as placeholder for lists you think will not change. Tuples are semantically limited in length, lists inherently might be of different length even if in one particular case they aren't.
- Compare the concepts of coordinates (always
(x, y)
,(x, y, z)
etc.) to a list of Django plugins. Coordinates never have different length, that would simply be a different data type, but different Django projects have different length of plugin lists even if your app's doesn't change.
- Compare the concepts of coordinates (always
Django database settings should have timeout to handle Kubernets services being down
check that imagemagick isn't used
Frontend code standards
Use yarn and webpack, not bower or grunt. Additional webpack modules need to go through architecture review.
Do not include node_modules in source. If you need to build something custom, build it properly so that any (sub)dependencies do not get locked. If you need to patch upstream versions, make PRs upstream and document the related PR to the repo so that the hacked version can be changed back to the official one later.
For React, use Redux as the store, redux-actions and redux-promise, saga or thunk. Separate API HTTP calls to a separate module/file.
React/JSX style guide
We follow Airbnb's style guide for React/JSX with few exceptions which are listed below. Use eslint with airbnb style guide.
The main difference between Helsinki and Airbnb style guides is in the file structure.
Folder structure
It is recommended to structure projects so that files are grouped together by feature or route.
If file count in a folder starts to get out of hand, it might be better to create subfolders for that folder (check product/
in the image below). Then again you should avoid having too much nesting.
There is no "right" answer here so it is up to you to find the folder structure that works best for the project. As projects grow larger, it might be necessary to rearrange files into a different folder structure. So choosing the “right” one, in the beginning, isn’t very important.
footer/Footer.jsxFooter.test.jsxfooter.cssuser/UserProfile.jsxuserProfile.cssUserList.jsxuserList.cssutils.jsproduct/page/ProductPage.jsxProductPage.test.jsxproductPage.csslist/ProductList.jsxProductList.test.jsxproductList.csscreate/CreateProduct.jsxCreateProduct.test.jsxcreateProduct.cssedit/EditProduct.jsxEditProduct.test.jsxeditProduct.csshome/home.jsxhome.css
Separating domain and common files
This section is based on DDD (Domain driven design)
The idea is that we should divide project files between /domain
and /common
-folders.
Domain: Domain files are the project specific files that are tied to the projects business logic.
Common: Common files are the files that aren't tied to the projects business logic. Basically, you could share these files between projects, and even maybe make them as npm packages. Though, this is not a requirement for a common file.
common/utils/APITools.jsbutton/Button.tsxbutton.cssform/textField/TextField.tsxtextField.cssradio/Radio.tsxradio.cssdomain/product/list/ProductList.tsxproductList.csspage/ProductPage.tsxproductPage.cssproductHelpers.tsuser/UserPage.tsxuserPage.csshome/Home.tsxhome.css
Naming
Airbnb style guide has root component name as folder name and the folder has index.jsx
file. This makes it possible to import the root component by just writing the folder name into import path. This is not something Helsinki style guide recommends.
Example below shows the main difference between Helsinki and Airbnb when importing components:
// Airbnb wayimport Footer from './Footer';// Helsinki wayimport Footer from './footer/Footer';
- Extensions: Use .jsx or .tsx extension for React components depending on if you use TypeScript or not. eslint: react/jsx-filename-extension
- Filenames
- Use PascalCase for component and component test filenames. E.g.,
ReservationCard.jsx
,ReservationCard.test.jsx
. - Use camelCase for style filenames. E.g.,
reservationCard.css
,reservationCard.less
. - Use camelCase for jsx and .tsx files that are not component files. E.g.,
index.jsx
,helper.jsx
,apiTools.jsx
- Use PascalCase for component and component test filenames. E.g.,
- Folder name: Use camelCase for folder names. E.g.,
./reservationCard
,./footer
. - Folder structure: Group folders by features. More detailed documentation here.
- Reference Naming: Use PascalCase for React components and camelCase for their instances. eslint: react/jsx-pascal-case
// badimport reservationCard from './reservationCard/ReservationCard';// goodimport ReservationCard from './reservationCard/ReservationCard';// badconst ReservationItem = <Reservation />;// goodconst reservationItem = <Reservation />;
- Component Naming: Use the filename as the component name. For example,
ReservationCard.jsx
should have a reference name of ReservationCard. Folder name should in most cases be the camelCase version of the component name reservationCard. Even though in some cases the folder name might be something more generic than the component name itself.
// badimport Footer from './Footer/Footer';// badimport Footer from './Footer/index';// badimport Footer from './Footer';// goodimport Footer from './footer/Footer';
General syntactic guides
- Promises: Try to use await/async when possible. Use await/async instead of Promises.
// badconst fetchAndDisplay = ({ url, element }) => {showLoadingSpinner();fetch(url).then((response) => response.text()).then((text) => {element.textContent = text;}).catch((error) => {element.textContent = error.message;}).finally(() => {hideLoadingSpinner();});};// goodconst fetchAndDisplay = async ({ url, element }) => {showLoadingSpinner();try {const response = await fetch(url);const text = await response.text();element.textContent = text;} catch (error) {element.textContent = error.message;} finally {hideLoadingSpinner();}};
Use finally
to avoid code duplication and to semantically
separate cleanup from the other parts.
If you need to run the Promises in parallel, use Promise.all()
(or Promise.race()
).
Form guidelines
Helsinki Design System offers two patterns that are related to form design and implementation. These patterns give general guidelines on how to build consistent, usable and accessible forms and how to utilise design system components for this. Form validation pattern also includes code examples in React using yup
validation library and HDS form components. While dynamic validation is the recommended method, HDS also has a static method available when dynamic validation is not possible.