У цій статті ми поговоримо про чистий код — його переваги, різні стандарти, принципи та загальні настанови щодо написання чистого коду.
Що таке чистий код
Чистий код — це набір правил і принципів, які допомагають полегшити читання, підтримку та розширення нашого коду. Це один з найважливіших аспектів якісного програмного забезпечення. Розробники більше часу читають код, ніж пишуть його, тому важливо, щоб код був якісним.
Написати код легко, але написати хороший і чистий код важко.
Код повинен бути простим і вільним від повторюваності. Хоча ми просто надаємо інструкції комп'ютеру, код все одно має бути виразним, тобто легко читатись і чітко повідомляти, для чого він призначений.
Важливість чистого коду
Написання чистого коду має багато переваг. Наприклад, такий код:
- легкий для розуміння;
- ефективніший;
- простіший в обслуговуванні, масштабуванні, налагодженні та рефакторингу. А ще для нього потрібно писати менше документації.
Стандарти написання коду
Це збірки правил кодування, настанов та найкращих практик. Кожна мова програмування має власні стандарти, яких слід дотримуватися, щоб писати чистіший код. Зазвичай вони стосуються:
- впорядкування файлів;
- принципів та практик програмування;
- форматування коду (відступів, оголошень, інструкцій);
- конвенцій іменування;
- коментарів.
PEP 8 (Пропозиція покращення коду Python)
PEP 8 — це стилістичні настанови про стандарти кодування для Python, найпопулярніший посібник у спільноті Python. Ось найважливіші правила: Конвенції іменування:
- Назви класів пишемо ВерблюдячимРегістром (
MyClass
). - Назви змінних вказуємо зміїним_регістром у нижньому регістрі (
first_name
). - Назви функцій теж пишемо зміїним_регістром у нижньому регістрі (
quick_sort()
). - Назви констант — зміїним_регістром у верхньому регістрі (
PI = 3.14159
). - Модулі слід називати коротко й зміїним_регістром у нижньому регістрі (
numpy
). - Одинарні та подвійні лапки трактуються однаково (виберіть одні та послідовно вживайте їх). Форматування рядків:
- Для відступу застосовуйте 4 пробіли (краще пробіли, а не відступ клавішею Tab).
- Рядки не повинні бути довшими за 79 символів.
- Уникайте кількох інструкцій в одному рядку.
- Визначення функцій і класів верхнього рівня виділяються двома порожніми рядками.
- Визначення методів всередині класу виділяються одним порожнім рядком.
- Імпорти розташовуються на окремих рядках. Пробіли:
- Уникайте зайвих пробілів у дужках.
- Уникайте кінцевих пропусків.
- Завжди виокремлюйте двійкові оператори одним пробілом по обидва боки.
- Якщо застосовано оператори з різними пріоритетами, подумайте про додавання пробілів навколо операторів з найнижчим пріоритетом.
- Не додавайте пробіли навколо знака =, коли вони вказують на аргумент ключового слова. Коментарі:
- Коментарі не повинні суперечити коду.
- Мають бути завершеними реченнями.
- Повинні містити пробіл після знаку # та починатися з великої букви.
- Багаторядкові коментарі, що вжиті у функціях (рядки документування), повинні мати короткий однорядковий опис, після якого пишеться розлогий опис. Докладніше в офіційному довіднику PEP 8.
Pythonic Code
Pythonic code — це набір ідіом, прийнятий спільнотою Python. З ним ви можете правильно застосовувати ідіоми та парадигми Python, щоб ваша програма була чистою, читабельною та швидко виконувалась. Pythonic code описує:
- хитрощі зі змінними;
- маніпулювання списком (ініціалізація, нарізання);
- роботу з функціями;
- зрозумілість коду;
Існує велика різниця між написанням коду Python та написанням коду Pythonic. Для коду Pythonic не можна просто ідіоматично перекласти іншу мову (наприклад, Java або C++) на Python; вам треба думати мовою Python.
Розгляньмо приклад. Нам потрібно скласти перші 10 чисел так:
1 + 2 + ... + 10
. Розв'язок не на Pythonic буде приблизно таким:
n = 10
sum_all = 0
for i in range(1, n + 1):
sum_all = sum_all + i
print(sum_all) # 55
А от розв'язок з Pythonic може мати такий вигляд:
n = 10
sum_all = sum(range(1, n + 1))
print(sum_all) # 55
Другий приклад значно простіше читати досвідченому розробнику Python, але тут потрібне глибше розуміння вбудованих функцій і синтаксису Python. Найпростіший спосіб написання коду Pythonic — пам'ятати про Дзен Пайтона в процесі та поступово вивчати стандартну бібліотеку Python.
Дзен Пайтона
Дзен Пайтона — це набір з 19 керівних принципів написання комп'ютерних програм на Python. Його створив у 1999 році інженер-програміст Тім Пітерс. Дзен включили до інтерпретатора Python як бонусне яйце-райце. Ви можете переглянути його, запустивши таку команду:
import this
>>> import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
Якщо вам цікаво значення цього «вірша», перегляньте Дзен Пайтона, там є пояснення до кожного рядка.
Принципи написання коду
Існує чимало принципів, яких ви можете дотримуватися, щоб писати кращий код: кожен з них має свої переваги, недоліки та компроміси. Ця стаття охоплює чотири найпопулярніших принципи: DRY, KISS, SoC, та SOLID.
DRY: не повторюйтеся
Кожна частина знань повинна мати єдине, однозначне, надійне представлення у системі. Це один з найпростіших принципів написання коду. Єдине його правило — не дублювати код. Замість дублювання рядків знайдіть алгоритм, який застосовує ітерацію. Код DRY легко підтримується. Ви можете розширити цей принцип за допомогою абстракції моделі/даних. Мінуси DRY — ймовірність отримати занадто багато абстракцій, створити зовнішні залежності та складний код. DRY також може спричинити ускладнення, якщо ви спробуєте змінити більший шматок вашої кодової бази. Ось чому не варто звертатись до DRY занадто рано. Завжди краще мати кілька повторюваних розділів коду, ніж неправильні абстракції.
KISS: пишіть коротше і простіше
Більшість систем найкраще працюють, якщо їх не ускладнювати. Принцип KISS стверджує, що більшість систем найкраще працюють, якщо вони простіші, а не ускладнені. Простота має бути ключовою метою у проєктуванні, а непотрібної складності слід уникати.
SoC: розділяйте завдання
SoC — це принцип проєктування для поділу комп'ютерної програми на окремі розділи, щоб кожен розділ розв'язував окреме завдання. Завдання — це сукупність інформації, яка впливає на код комп'ютерної програми. Чудовим прикладом SoC є MVC (модель–представлення–контролер). Якщо ви вирішили скористатися цим підходом, будьте обережні, щоб не розділити застосунок на занадто багато модулів. Створювати новий модуль варто, лише коли це має сенс. Більше модулів означає більше проблем.
SOLID
SOLID — це мнемонічна абревіатура п'яти принципів проєктування, призначених, щоб зробити проєкти програмного забезпечення зрозумілішими, гнучкішими та простішими для обслуговування. SOLID надзвичайно корисний під час написання ООП-коду. У ньому йдеться про поділ вашого класу на кілька підкласів, успадковування, абстрагування, інтерфейси тощо. Є п'ять основних принципів:
- Принцип єдиного обов'язку: «Кожен клас повинен виконувати лише один обов'язок».
- Принцип відкритості/закритості: «Програмні сутності повинні бути відкритими для розширення, але закритими для змін».
- Принцип підставлення Лісков: «Функції, які застосовують вказівники або посилання на базові класи, повинні мати можливість використовувати об'єкти похідних класів, не знаючи про це».
- Принцип розділення інтерфейсу: «Багато спеціалізованих інтерфейсів краще за один універсальний».
- Принцип інверсії залежностей: «Залежності всередині системи будуються на основі абстракцій, що не повинні залежати від деталей».
Упорядники коду
Упорядники коду змінюють стиль кодування через автоматичне форматування, вони допомагають писати та підтримувати чистий код. Більшість із них дають змогу створити файл конфігурації стилю, яким можна поділитися з колегами. Найпопулярніші упорядники коду Python:
- black;
- flake8;
- autopep8;
- yapf. Більшість сучасних IDE також включають лінтери, які працюють у фоновому режимі під час введення тексту та допомагають виявляти невеликі хиби кодування, помилки, небезпечні шаблони коду та зберігати код упорядкованим. Існує два типи лінтерів: логічні та стилістичні. Найпопулярніші лінтери для Python:
- Pylint;
- PyFlakes;
- mypy. Докладніше про лінтери та форматування коду у статті Python Code Quality.
Конвенції іменування
Одним з найважливіших аспектів чистого коду є умовне йменування. Ви завжди повинні використовувати змістовні назви, що розкривають призначення коду. Завжди краще вжити довгі описові назви, ніж короткі назви з коментарями.
# Поганий код
# represents the number of active users
au = 55
# Хороший код
active_user_amount = 55
Більше прикладів ми розглянемо у наступних двох розділах.
Змінні
1. Використовуйте іменники для назв змінних
2. Вживайте описові назви, що пояснюють призначення коду
Інші розробники мають зрозуміти, для чого ця змінна, просто прочитавши її назву.
# Поганий код
c = 5
d = 12
# Хороший код
city_counter = 5
elapsed_time_in_days = 12
3. Використовуйте легковимовні назви
Інакше вам буде важко вголос пояснити свої алгоритми.
from datetime import datetime
# Поганий код
genyyyymmddhhmmss = datetime.strptime('04/27/95 07:14:22', '%m/%d/%y %H:%M:%S')
# Хороший код
generation_datetime = datetime.strptime('04/27/95 07:14:22', '%m/%d/%y %H:%M:%S')
4. Уникайте багатозначних скорочень
Не намагайтеся придумувати власні скорочення. Краще, щоб змінна мала довшу назву, яка б не спантеличувала інших розробників.
# Поганий код
fna = 'Bob'
cre_tmstp = 1621535852
# Хороший код
first_name = 'Bob'
creation_timestamp = 1621535852
5. Завжди використовуйте один і той же словниковий запас
Уникайте синонімів у назвах змінних.
# Поганий код
client_first_name = 'Bob'
customer_last_name = 'Smith'
# Хороший код
client_first_name = 'Bob'
client_last_name = 'Smith'
6. Не використовуйте «містичні числа»
Містичні числа — це незрозумілі числа у коді, які не мають зрозумілого значення. Розгляньмо приклад:
import random
# Поганий код
def roll():
return random.randint(0, 36) # що має представляти 36?
# Хороший код
ROULETTE_POCKET_COUNT = 36
def roll():
return random.randint(0, ROULETTE_POCKET_COUNT)
Замість містичних чисел ми можемо створити цілком змістовну змінну.
7. Використовуйте доменні імена-розв'язки
Якщо ви використовуєте у своєму алгоритмі чи класі багато різних типів даних і не можете здогадатися про їхнє призначення з назви самої змінної, не бійтеся додати до назви змінної суфікс типу даних. Наприклад:
# Хороший код
score_list = [12, 33, 14, 24]
word_dict = {
'a': 'apple',
'b': 'banana',
'c': 'cherry',
}
А ось поганий приклад (оскільки ви не можете зрозуміти тип даних за назвою змінної):
# Поганий код
names = ["Nick", "Mike", "John"]
8. Уникайте зайвого контексту
Не додавайте непотрібні дані до назв змінних, особливо якщо ви працюєте з класами.
# Поганий код
class Person:
def __init__(self, person_first_name, person_last_name, person_age):
self.person_first_name = person_first_name
self.person_last_name = person_last_name
self.person_age = person_age
# Хороший код
class Person:
def __init__(self, first_name, last_name, age):
self.first_name = first_name
self.last_name = last_name
self.age = age
Ми вже всередині класу Person
, тому немає потреби додавати префікс person_
до кожної змінної класу.
Функції
1. Використовуйте дієслова для назв функцій
2. Не використовуйте різні слова для одного поняття
Виберіть слово для кожного поняття і постійно вживайте його. Інакше буде плутанина.
# Поганий код
def get_name(): pass
def fetch_age(): pass
# Хороший код
def get_name(): pass
def get_age(): pass
3. Пишіть короткі й прості функції
4. Функції повинні виконувати одне завдання
Якщо ваша функція містить ключове слово «and», ви, ймовірно, можете розділити його на дві функції. Розгляньмо приклад:
# Поганий код
def fetch_and_display_personnel():
data = # ...
for person in data:
print(person)
# Хороший код
def fetch_personnel():
return # ...
def display_personnel(data):
for person in data:
print(person)
Функції повинні виконувати одне завдання, і, як читач, вони роблять те, що ви від них очікуєте.
Є хороше емпіричне правило: на розуміння будь-якої функції має витрачатися не більше ніж кілька хвилин. Поверніться і перегляньте старий код, написаний вами кілька місяців тому. Ймовірно, вам слід переробити кожну функцію, для розуміння якої вам потрібно понад п'ять хвилин. Адже це ваш код. Подумайте, скільки часу знадобиться іншому розробнику, щоб його осягнути.
5. Обмежуйте кількість аргументів
Кількість аргументів у функції має бути мінімальною. В ідеалі ваші функції повинні мати лише один-два аргументи. Якщо вам потрібно надати додаткові аргументи функції, ви можете створити об'єкт config, який передається функції. Або ж просто розділіть її на кілька функцій. Приклад:
# Поганий код
def render_blog_post(title, author, created_timestamp, updated_timestamp, content):
# ...
render_blog_post("Clean code", "Nik Tomazic", 1622148362, 1622148362, "...")
# Хороший код
class BlogPost:
def __init__(self, title, author, created_timestamp, updated_timestamp, content):
self.title = title
self.author = author
self.created_timestamp = created_timestamp
self.updated_timestamp = updated_timestamp
self.content = content
blog_post1 = BlogPost("Clean code", "Nik Tomazic", 1622148362, 1622148362, "...")
def render_blog_post(blog_post):
# ...
render_blog_post(blog_post1)
6. Не використовуйте прапори у функціях
Прапори — це змінні (зазвичай логічні), які передаються функціям і завдяки яким функція визначає свою поведінку. Їх небажано вживати, оскільки функції повинні виконувати лише одне завдання. Найпростіший спосіб уникнути прапорів — розділити вашу функцію на кілька менших.
text = "This is a cool blog post."
# Поганий код
def transform(text, uppercase):
if uppercase:
return text.upper()
else:
return text.lower()
uppercase_text = transform(text, True)
lowercase_text = transform(text, False)
# Хороший код
def uppercase(text):
return text.upper()
def lowercase(text):
return text.lower()
uppercase_text = uppercase(text)
lowercase_text = lowercase(text)
7. Уникайте побічних ефектів
Побічний ефект виникає, якщо функція виконує щось ще, окрім прийняття значення і повернення іншого значення або значень. Наприклад, побічним ефектом може бути запис до файлу або зміна глобальної змінної.
Коментарі
Як би ми не намагалися писати чистий код, все одно будуть частини програми, які потрібно додатково пояснити. Коментарі дають змогу швидко розповісти іншим розробникам (і собі у майбутньому), чому ми написали це саме так. Майте на увазі, що надмірна кількість коментарів може зробити ваш код ще гіршим, ніж до того. Яка різниця між коментарями до коду та документацією?
Тип | Відповіді | Зацікавлена сторона |
---|---|---|
Документація | Коли та як | Користувачі |
Коментарі у коді | Чому | Розробники |
Чистий код | Що | Розробники |
Докладніше про різницю між коментарями та документацією у статті Documenting Python Code and Projects. |
1. Не коментуйте погано написаний код, перепишіть його
Коментування погано написаного коду, наприклад # TODO: RE-WRITE THIS TO BE BETTER
, допоможе лише у короткотерміновій перспективі. Рано чи пізно колегам доведеться попрацювати з вашим кодом, і вони зрештою перепишуть його, витративши кілька годин на те, аби зрозуміти, що він робить.
2. Читабельний код не потребує коментарів
Якщо ваш код досить читабельний, коментарі не потрібні: зайві коментарів лише заважатимуть. Ось поганий приклад:
# Перевірка, чи не існує користувач із таким ідентифікатором.
if not User.objects.filter(id=user_id).exists():
return Response({
'detail': 'The user with this ID does not exist.',
})
За загальним правилом, якщо вам потрібно додати коментарі, вони повинні пояснювати «чому» ви щось зробили, а не «що» відбувається.
3. Не пишіть зайвих коментарів
Не пишіть коментарі, які не додають до коду нічого цінного. Це погано:
numbers = [1, 2, 3, 4, 5]
# Ця змінна зберігає середнє значення списку чисел.
average = sum(numbers) / len(numbers)
print(average)
Це теж погано:
4. Використовуйте правильні типи коментарів
Більшість мов програмування мають різні типи коментарів. Вивчіть їхні відмінності та вживайте їх відповідно. Вам також слід вивчити синтаксис документації з коментарями. Хороший приклад:
def model_to_dict(instance, fields=None, exclude=None):
"""
Returns a dict containing the data in ``instance`` suitable for passing as
a Form's ``initial`` keyword argument.
``fields`` is an optional list of field names. If provided, return only the
named.
``exclude`` is an optional list of field names. If provided, exclude the
named from the returned dict, even if they are listed in the ``fields``
argument.
"""
opts = instance._meta
data = {}
for f in chain(opts.concrete_fields, opts.private_fields, opts.many_to_many):
if not getattr(f, 'editable', False):
continue
if fields is not None and f.name not in fields:
continue
if exclude and f.name in exclude:
continue
data[f.name] = f.value_from_object(instance)
return data
5. Не залишайте закоментований код
Найгірше, що ви можете зробити, це залишити закоментований код у своїх програмах. Увесь код налагодження або повідомлення про налагодження слід видалити, перш ніж надіслати його до системи контролю версій, інакше ваші колеги будуть боятися видалити його, і ваш закоментований код залишиться там назавжди.
Декоратори, контекстні менеджери, ітератори та генератори
У цьому розділі ми розглянемо деякі концепції та хитрощі Python, які ми можемо застосовувати для написання кращого коду.
Декоратори
Декоратори — це надзвичайно потужний інструмент у Python, який дозволяє нам додати до функції певну власну функціональність. По суті, це просто функції, які називаються внутрішніми функціями. Застосовуючи їх, ми використовуємо принцип SoC (розділення проблем) та робимо наш код модульним. Вивчіть їх — і ви будете ближчими до коду Pythonic! Скажімо, у нас є сервер, захищений паролем. Ми можемо або попросити пароль у кожному методі сервера, або створити декоратор і захистити наші методи сервера так:
def ask_for_passcode(func):
def inner():
print('What is the passcode?')
passcode = input()
if passcode != '1234':
print('Wrong passcode.')
else:
print('Access granted.')
func()
return inner
@ask_for_passcode
def start():
print("Server has been started.")
@ask_for_passcode
def end():
print("Server has been stopped.")
start() # декоратор запитає пароль
end() # декоратор запитає пароль
Тепер наш сервер питатиме пароль щоразу, коли викликається start()
або end()
.
Контекстні менеджери
Контекстні менеджери спрощують нашу взаємодію із зовнішніми ресурсами, як-от файли та бази даних. Найпоширеніший метод — застосувати оператор with
. Хороше в ньому те, що він автоматично звільняє пам'ять за межами свого блоку.
Погляньмо на приклад:
with open('wisdom.txt', 'w') as opened_file:
opened_file.write('Python is cool.')
# opened_file було закрито.
Без контекстного менеджера наш код виглядав би так:
file = open('wisdom.txt', 'w')
try:
file.write('Python is cool.')
finally:
file.close()
Ітератори
Ітератор — це об'єкт, що містить злічувану кількість значень. Ітератори дозволяють повторювати об'єкт, а це означає, що ви можете проходити через усі значення.
Скажімо, ми маємо перелік імен і хочемо пройти його циклом. Ми можемо зробити це за допомогою next(names)
:
names = ["Mike", "John", "Steve"]
names_iterator = iter(names)
for i in range(len(names)):
print(next(names_iterator))
Або скористатися розширеним циклом:
names = ["Mike", "John", "Steve"]
for name in names:
print(name)
Усередині розширених циклів уникайте вживання назв змінних, як-от item або value, оскільки з ними складніше визначити, що саме зберігає змінна, особливо у вкладених розширених циклах.
Генератори
Генератор — це функція в Python, яка повертає об'єкт-ітератор замість одного єдиного значення. Основна відмінність між звичайними функціями та генераторами полягає в тому, що генератори використовують ключове слово yield
замість return
. Кожне наступне значення в ітераторі отримується за допомогою next(generator)
.
Скажімо, ми хочемо створити перші n
множників для x
. Наш генератор матиме приблизно такий вигляд:
def multiple_generator(x, n):
for i in range(1, n + 1):
yield x * i
multiples_of_5 = multiple_generator(5, 3)
print(next(multiples_of_5)) # 5
print(next(multiples_of_5)) # 10
print(next(multiples_of_5)) # 15
Модульність та класи
Щоб ваш код був максимально впорядкованим, слід розділити його на кілька файлів, які потім розбиваються на різні каталоги. Якщо ви пишете код ООП-мовою вам також слід дотримуватися основних принципів ООП, як-от інкапсуляція, абстракція, успадкування та поліморфізм. Розподіл коду на кілька класів полегшить його розуміння та підтримку. Немає чіткого правила щодо довжини файлу або класу, але намагайтеся робити їх невеликими (бажано до 200 рядків). Усталена структура проєктів Django є хорошим прикладом структурованості коду:
awesomeproject/
├── main/
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── blog/
│ ├── migrations/
│ │ └── __init__.py
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── models.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
└── templates
Django — це фреймворк MTV (Модель – Шаблон – Представлення). Цей шаблон ділить логіку програми на три взаємопов'язані частини. Ви можете побачити, що кожен застосунок перебуває в окремому каталозі, а кожен файл обслуговує одне конкретне завдання. Якщо ваш проєкт розділений на кілька застосунків, переконайтеся, що вони не занадто залежать один від одного.
Тестування
Якісне програмне забезпечення не обходиться без тестів. Тестування програмного забезпечення дозволяє нам виявляти вади та помилки до розгортання ПЗ. Тести такі ж важливі, як і робочий код, і ви повинні витратити достатньо часу на роботу з ними. Докладніше про тестування та написання чистого тестового коду у цих статтях:
- Тестування у Python (англ.).
- Сучасна розробка з тестуванням у Python (англ.).
Висновок
Писати чистий код складно. Немає єдиного плану, щоб все було добре: для майстерності потрібен час і досвід. Ми розглянули деякі стандарти та загальні вказівки, які можуть допомогти вам написати кращий код. Одна з найкращих порад, які ми можемо вам дати: будьте послідовним і намагайтеся писати простий код, який легко тестувати. Якщо код важко тестувати, його, ймовірно, важко використовувати. Якщо ви хочете дізнатися більше, ознайомтесь із Вичерпним посібником із розробки Python (мовою оригіналу), де ви дізнаєтесь, як писати чистий код на практиці.
Ще немає коментарів