|
1 | 1 | # Evrone Python Guidelines |
2 | 2 |
|
3 | | - |
4 | | - |
| 3 | +This repo contains guidelines that Evrone Python team uses when writes the code. |
| 4 | +Read guidelines carefully and keep them under pillow. |
5 | 5 |
|
| 6 | +Coding guidelines are accessible in different languages: |
| 7 | +- [English](/EN.md) |
| 8 | +- [Russian](/RU.md) |
6 | 9 |
|
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 | 10 |
|
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 | | - |
215 | | -``` |
216 | | -Дифф 444 + 333 = 777 |
217 | | -``` |
218 | | - |
219 | | -Хорошо ✅: |
220 | | - |
221 | | - |
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 | | -## Спонсор |
| 11 | +## Sponsor |
369 | 12 | [<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