Skip to content

Commit ff405fd

Browse files
authored
Update RU.md
1 parent 61ce333 commit ff405fd

File tree

1 file changed

+368
-0
lines changed

1 file changed

+368
-0
lines changed

RU.md

Lines changed: 368 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,369 @@
1+
# Evrone Python Guidelines
12

3+
![GitHub last commit](https://img.shields.io/github/last-commit/evrone/evrone-python-guidelines?logo=GitHub)
4+
![GitHub release (latest by date)](https://img.shields.io/github/v/release/evrone/evrone-python-guidelines)
5+
6+
7+
## Содержание
8+
- [Про код](#про-код)
9+
- [Основные принципы](#основные-принципы)
10+
- [Атомарность операций](#атомарность-операций)
11+
- [Логические блоки](#логические-блоки)
12+
- [Размеры методов, функций и модулей](#размеры-методов-функций-и-модулей)
13+
- [Импорты](#импорты)
14+
- [Файлы `__init__.py`](#файлы-__init__py)
15+
- [Докстринги](#докстринги)
16+
- [Про Pull Request](#про-pull-request)
17+
- [Создание Pull Request](#создание-pull-request)
18+
- [Рефакторинг и Pull Request](#рефакторинг-и-pull-request)
19+
- [Размер Pull Request](#размер-pull-request)
20+
- [Про тулинг](#про-тулинг)
21+
- [Тестирование (pytest)](#тестирование-pytest)
22+
- [Пакетный менеджер (poetry)](#пакетный-менеджер-poetry)
23+
- [Форматирование кода (Black)](#форматирование-кода-black)
24+
- [Форматирование импортов (isort)](#форматирование-импортов-isort)
25+
- [Линтер (flake8)](#линтер-flake8)
26+
- [Тайп-чекер (mypy)](#тайп-чекер-mypy)
27+
- [Pre-commit хуки (pre-commit)](#pre-commit-хуки-pre-commit)
28+
- [Прочее](#прочее)
29+
- [Документация к REST API](#документация-к-rest-api)
30+
31+
32+
## Про код
33+
34+
### Основные принципы
35+
- **Поддерживаемость** (представьте, сможете ли вы понять свой код через год или через два)
36+
- **Простота** (между сложным и простым решением следует выбрать простое)
37+
- **Очевидность** (представьте, когда подключится новый программист, насколько ему будет понятно, почему именно **так** написан этот код)
38+
39+
40+
### Атомарность операций
41+
**1 действие ~ 1 строка**
42+
43+
Постарайтесь делать атомарные операции в коде - на каждой строке ровно **одно** действие.
44+
45+
Плохо ❌:
46+
```python
47+
# 1. 3 действия на одной строке - 3 вызова функции
48+
foo_result = foo(bar(spam(x)))
49+
50+
# 2. 3 действия на одной строке - вызов функции foo, get_c, from_b
51+
foo_result = foo(a=a, b=b, c=get_c(from_b())
52+
53+
# 3. 3 действия на одной строке - фильтрация по аргументам, условное получение элементов (через or), вызов метода .value
54+
result = [(a.value() or A, b or B) for a, b in iterator if a < b]
55+
56+
# 4. 4 действия на одной строке - из библиотеки / переменной foo идет получение атрибута bar, получение атрибута spam, получение атрибута hello и вызов calculate_weather
57+
result = calculate_weather(foo.bar.spam.hello)
58+
```
59+
60+
Хорошо ✅:
61+
```python
62+
# 1. делаем поочередный вызов каждой функции
63+
spam_result = spam(x)
64+
bar_result = bar(spam_result)
65+
foo_result = foo(bar_result)
66+
67+
# 2. поочередно вызываем функции, результат пишем в переменную и используем ее при вызове foo
68+
from_b_result = from_b()
69+
c = get_c(from_b_result)
70+
foo_result = foo(a=a, b=b, c=c)
71+
72+
# 3. последовательно проводим действия над списком - вначале фильтруем, вызываем метод .value у a, выбираем между элементами (or)
73+
filtered_result = ((a, b) for a, b in iterator if a < b)
74+
intermediate_result = ((a.value(), b) for a, b in filtered_result)
75+
result = [(a or A, b or B) for a, b in intermediate_result]
76+
77+
# 4 . последовательно читаем атрибуты bar, spam, hello и вызываем функцию calculate_weather
78+
bar = foo.bar
79+
spam = bar.spam
80+
hello = spam.hello
81+
result = calculate_weather(hello)
82+
```
83+
84+
85+
**Почему?** Потому что код становится более читабельным, не нужно исполнять несколько выражений в голове во время чтения кода. Разбитый до простых атомных операций код воспринимается гораздо лучше, чем сложный уан-лайнер. Постарайтесь упростить свой код настолько, насколько это возможно - код чаще читается, чем пишется.
86+
87+
88+
### Логические блоки
89+
90+
Постарайтесь делить код на логические блоки - так глазу программиста будет в разы проще прочитать и уловить суть.
91+
92+
Плохо ❌:
93+
```python
94+
def register_model(self, app_label, model):
95+
model_name = model._meta.model_name
96+
app_models = self.all_models[app_label]
97+
if model_name in app_models:
98+
if (model.__name__ == app_models[model_name].__name__ and
99+
model.__module__ == app_models[model_name].__module__):
100+
warnings.warn(
101+
"Model '%s.%s' was already registered. "
102+
"Reloading models is not advised as it can lead to inconsistencies, "
103+
"most notably with related models." % (app_label, model_name),
104+
RuntimeWarning, stacklevel=2)
105+
else:
106+
raise RuntimeError(
107+
"Conflicting '%s' models in application '%s': %s and %s." %
108+
(model_name, app_label, app_models[model_name], model))
109+
app_models[model_name] = model
110+
self.do_pending_operations(model)
111+
self.clear_cache()
112+
```
113+
114+
Хорошо ✅:
115+
```python
116+
def register_model(self, app_label, model):
117+
model_name = model._meta.model_name
118+
app_models = self.all_models[app_label]
119+
120+
if model_name in app_models:
121+
if (
122+
model.__name__ == app_models[model_name].__name__ and
123+
model.__module__ == app_models[model_name].__module__
124+
):
125+
warnings.warn(
126+
"Model '%s.%s' was already registered. "
127+
"Reloading models is not advised as it can lead to inconsistencies, "
128+
"most notably with related models." % (app_label, model_name),
129+
RuntimeWarning, stacklevel=2)
130+
131+
else:
132+
raise RuntimeError(
133+
"Conflicting '%s' models in application '%s': %s and %s." %
134+
(model_name, app_label, app_models[model_name], model))
135+
136+
app_models[model_name] = model
137+
138+
self.do_pending_operations(model)
139+
self.clear_cache()
140+
```
141+
142+
**Почему?** Кроме того, что это повышает читабельность, [Zen of Python](https://www.python.org/dev/peps/pep-0020/) рассказывает нам о том, как надо писать идиоматический код на Python.
143+
Одно из высказываний звучит как "Sparse is better than dense." - "Разреженное лучше чем сжатое". Сжатый код сложнее прочитать чем разреженный.
144+
145+
146+
### Размеры методов, функций и модулей
147+
148+
Предельный размер метода или функции - **50** строк.
149+
Достижение предельного размера говорит о том, что функция (метод) делает слишком много - декомпозируйте действия внутри функции (метода).
150+
151+
152+
Предельный размер модуля - **300** строк.
153+
Достижение предельного размера говорит о том, что модуль получил слишком много логики - декомпозируйте модуль на несколько.
154+
155+
Длина строки - 100 символов.
156+
157+
158+
### Импорты
159+
160+
Рекомендуемый способ импортирования - абсолютный.
161+
162+
Плохо ❌:
163+
```python
164+
# spam.py
165+
from . import foo, bar
166+
```
167+
168+
Хорошо ✅:
169+
```python
170+
171+
# spam.py
172+
from some.absolute.path import foo, bar
173+
```
174+
175+
**Почему?** Потому что абсолютный импорт явно определяет локацию (путь) модуля, который импортируется. При релативном
176+
импорте всегда нужно помнить путь и вычислять в уме локацию модулей `foo.py`, `bar.py` относительно `spam.py`
177+
178+
179+
### Файлы `__init__.py`
180+
181+
В `__init__.py` файлах пишем только импорты.
182+
183+
**Почему?** Потому что `__init__.py` - последнее место, в которое посмотрит программист, который будет читать код в будущем.
184+
185+
186+
### Докстринги
187+
Рекомендуем добавлять докстринги в функции, методы и классы.
188+
189+
**Почему?** Потому что программист, который впервые увидит ваш код, сможет быстрее понять, что в нем происходит.
190+
Код читается намного больше, чем пишется.
191+
192+
193+
## Про Pull Request
194+
195+
### Создание Pull Request
196+
**1 Pull Request = 1 issue**
197+
198+
Один Pull Request должен решать ровно одно issue.
199+
200+
**Почему?** Потому что ревьюверу сложнее держать контекст нескольких задач в голове и переключаться между ними. Когда PR содержит несколько issue - это часто приводит к тому, что PR увеличивается и требует больше времени и сил на ревью от ревьювера.
201+
202+
203+
### Рефакторинг и Pull Request
204+
Рефакторинг лучше всего выносить в отдельный Pull Request.
205+
206+
**Почему?** Когда рефакторинг идет вместе с решением определенного issue, то рефакторинг размывает контекст issue и вводит правки, которые не имеют отношения к данному PR.
207+
208+
209+
### Размер Pull Request
210+
Итоговый дифф PR не должен превышать +/- 600 измененных строк.
211+
212+
Плохо ❌:
213+
214+
![bad](https://user-images.githubusercontent.com/8825727/113953748-6fc7ba80-9853-11eb-9673-827995e54f73.png)
215+
```
216+
Дифф 444 + 333 = 777
217+
```
218+
219+
Хорошо ✅:
220+
221+
![good](https://user-images.githubusercontent.com/8825727/113953831-a30a4980-9853-11eb-854b-d4c4f6559f2c.png)
222+
```
223+
Дифф 222 + 111 = 333
224+
```
225+
226+
227+
**Почему?** Потому что чем больше PR - тем более он становится неконтролируемым и мерж производится "закрыв глаза и заткнув уши".
228+
Также, большинству ревьюверов будет сложно воспринять такой объем изменений за один раз.
229+
230+
231+
## Про тулинг
232+
233+
### Тестирование (pytest)
234+
[pytest](https://pytest.org) - фреймворк для тестирования кода
235+
236+
Рекомендуемый конфиг в `pytest.ini`:
237+
```ini
238+
[pytest]
239+
DJANGO_SETTINGS_MODULE = settings.local
240+
python_files = tests.py test_*.py *_tests.py
241+
242+
```
243+
244+
### Пакетный менеджер (poetry)
245+
246+
[poetry](https://python-poetry.org) - менеджер зависимостей и сборщик пакетов
247+
248+
249+
### Форматирование кода (Black)
250+
[Black](https://black.readthedocs.io/en/stable/) - автоформаттер кода по PEP8
251+
252+
Рекомендуемый конфиг в `pyproject.toml`:
253+
```toml
254+
255+
[tool.black]
256+
line-length = 100
257+
target-version = ['py38']
258+
exclude = '''
259+
(
260+
\.eggs
261+
|\.git
262+
|\.hg
263+
|\.mypy_cache
264+
|\.nox
265+
|\.tox
266+
|\.venv
267+
|_build
268+
|buck-out
269+
|build
270+
|dist
271+
)
272+
'''
273+
274+
```
275+
276+
277+
### Форматирование импортов (isort)
278+
[isort](https://pycqa.github.io/isort/) - автоформаттер блока импортов
279+
280+
Рекомендуемый конфиг в `pyproject.toml`:
281+
```toml
282+
283+
[tool.isort]
284+
line_length = 100
285+
sections = ["FUTURE", "STDLIB", "DJANGO", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"]
286+
multi_line_output = 3
287+
known_django = "django"
288+
profile = "django"
289+
src_paths = "app"
290+
lines_after_imports = 2
291+
292+
```
293+
294+
295+
### Линтер (flake8)
296+
[flake8](https://flake8.pycqa.org/en/latest/) - валидатор соответствия PEP8
297+
298+
Рекомендуемый конфиг в `.flake8`:
299+
```ini
300+
[flake8]
301+
max-line-length = 100
302+
max-complexity = 5
303+
exclude = .venv,venv,**/migrations/*,snapshots
304+
per-file-ignores =
305+
tests/**: S101
306+
**/tests/**: S101
307+
```
308+
309+
310+
### Тайп-чекер (mypy)
311+
312+
[mypy](http://mypy.readthedocs.io) - чекер для статической типизации
313+
314+
Рекомендуемый конфиг `mypy.ini`:
315+
316+
```ini
317+
[mypy]
318+
ignore_missing_imports = True
319+
allow_untyped_globals = True
320+
321+
[mypy-*.migrations.*]
322+
ignore_errors = True
323+
324+
```
325+
326+
327+
### Pre-commit хуки (pre-commit)
328+
329+
[pre-commit](https://pre-commit.com) - фреймворк для управления `pre-commit` хуками
330+
331+
Рекомендуемый конфиг `.pre-commit-config.yaml`:
332+
333+
```yaml
334+
default_language_version:
335+
python: python3.8
336+
337+
repos:
338+
- repo: local
339+
hooks:
340+
- id: black
341+
name: black
342+
entry: black app
343+
language: python
344+
types: [python]
345+
346+
- id: isort
347+
name: isort
348+
entry: isort app
349+
language: python
350+
types: [python]
351+
352+
- id: flake8
353+
name: flake8
354+
entry: flake8 server
355+
language: python
356+
types: [python]
357+
```
358+
359+
360+
## Прочее
361+
362+
### Документация к REST API
363+
Рекомендуемый формат документации - [OpenAPI](https://www.openapis.org).
364+
Схема для OpenAPI должна генерироваться "на лету", чтобы обеспечивать клиентов API свежими изменениями.
365+
366+
**Почему?** Потому что это один из распространенных форматов для документирования REST API, который вышел из Swagger. Данный формат документации поддерживается большим количеством клиентов (Swagger, Postman, Insomnia Designer и многие другие). Также, рукописная документация имеет свойство быстро устаревать, а документация, которая генерируется напрямую из кода позволяет не думать о постоянном обновлении документации.
367+
368+
## Спонсор
369+
[<img src="https://evrone.com/logo/evrone-sponsored-logo.png" width=300>](https://evrone.com/?utm_source=github.com&utm_campaign=evrone-python-codestyle)

0 commit comments

Comments
 (0)