Чистый код
Владимир Селюх
Разработчик ПО
Что мы сегодня обсудим?
1. Что такое чистый код?
2. Лучшие практики(советы) для:
-
Именование переменных
-
Написания функций и классов
-
Написания комментариев
-
Использование структур данных
-
Чистую Архитектуру ПО
Good, Bad and Ugly Code

Основной парадокс
На то что б писать чистый код нету времени потому что есть график, менеджер, дедлайны, etc...
Чистый код. Что это?
Мнение экспертов:
-
Читабельность
-
Легкость изменения
-
Расширяемость
-
Тестируемость
Правило бойскаута
Оставь место стоянки чище, чем оно было до твоего прихода.
Грязный код. Признаки.
Мнение всех кого не спроси:
-
На понимание кода тратится очень много времени
-
Код требует обильных комментариев
-
Высокая связность кода между компонентами системы, затрудняющая понимание происходящего
-
Дублирование фрагментов кода
-
Бесконечный скролл одной функции в высоту и ширину
Грязный код. Отмазки.Причины.
Что я слышал(или говорил):
-
Менеджер сказал это очень срочно!
-
Оно же и так работает!
-
Я дебажил 3 дня. Там или один ифчик или все переписывать!
-
ТЗ было плохое и я 3 раза переделывал!
-
Тут сама архитектура не правильная и вообще много кто до меня писал еще хуже!

Лучший способ писать чистый код это использовать сорокалетний опыт индустрии и здравый смысл. В. И. Ленин©
Именование переменных(общее)
-
Имена должны передавать намерения программиста
-
Избегайте ложных ассоциаций, затемняющих смысл кода
-
Используйте осмысленные различия. Избегайте неинформативных слов (ProductData, ProductInfo)
-
Используйте удобопроизносимые имена
-
Выбирайте имена, удобные для поиска
-
Избегайте схем кодирования имен
-
Избегайте мысленный преобразований: не заставляйте читателя мысленно преобразовывать ваши имена в другие.

from model.cpsr import CPSR
from model.company import CPSR as CPreSR
def get_sum (invoice):
service_list = CPreSR.get_service_models(invoice)
if not isinstance(service_list, dict):
raise
service_list = CPSR.remove_superfluous_free_services(service_list)
return reduce(lambda acc, x: acc + x['sum'], service_list)from model.cpsr import SecurityRequest
from model.company import CompanyServiceRecordИзбегайте схем кодирования имен.
Избегайте мысленный преобразований:
не заставляйте читателя мысленно преобразовывать ваши имена в другие.
from model.cpsr import CPSR
from model.company import CPSR as CPreSR
from model.cpsr import CPSR # Избегайте схем кодирования имен
# Избегайте мысленный преобразований:
# не заставляйте читателя мысленно преобразовывать ваши имена в другие.
from model.company import CPSR as CPreSR
# Имена должны передавать намерения программиста
def get_sum (invoice): #Выбирайте имена, удобные для поиска.
# список по английски list ну это же список всех сервисов, че?
service_list = CPreSR.get_service_models(invoice)
# Избегайте ложных ассоциаций, затемняющих смысл кода.
# service_list это dict
if not isinstance(service_list, dict):
raise
# Используйте удобопроизносимые имена.
service_list = CPSR.remove_superfluous_free_services(service_list)
# superfluous переводится как не нужный,
# но как это слово вообще произносится ???? о_О
return reduce(lambda acc, x: acc + x['sum'], service_list)BAD
Выбирайте имена, удобные для поиска.
Имена должны передавать намерения программиста
Maybe Better
def get_sum (invoice):
def get_invoice_services_price_sum (invoice):
from model.cpsr import CPSR # Избегайте схем кодирования имен
# Избегайте мысленный преобразований:
# не заставляйте читателя мысленно преобразовывать ваши имена в другие.
from model.company import CPSR as CPreSR
# Имена должны передавать намерения программиста
def get_sum (invoice): #Выбирайте имена, удобные для поиска.
# список по английски list ну это же список всех сервисов, че?
service_list = CPreSR.get_service_models(invoice)
# Избегайте ложных ассоциаций, затемняющих смысл кода.
# service_list это dict
if not isinstance(service_list, dict):
raise
# Используйте удобопроизносимые имена.
service_list = CPSR.remove_superfluous_free_services(service_list)
# superfluous переводится как не нужный,
# но как это слово вообще произносится ???? о_О
return reduce(lambda acc, x: acc + x['sum'], service_list)BAD
Избегайте ложных ассоциаций, затемняющих смысл кода.
Maybe Better
invoice_services = CompanyServiceRecord.get_service_models(invoice)
service_list = CPreSR.get_service_models(invoice) # service_list это dict
if not isinstance(service_list, dict):
raise
from model.cpsr import CPSR # Избегайте схем кодирования имен
# Избегайте мысленный преобразований:
# не заставляйте читателя мысленно преобразовывать ваши имена в другие.
from model.company import CPSR as CPreSR
# Имена должны передавать намерения программиста
def get_sum (invoice): #Выбирайте имена, удобные для поиска.
# список по английски list ну это же список всех сервисов, че?
service_list = CPreSR.get_service_models(invoice)
# Избегайте ложных ассоциаций, затемняющих смысл кода.
# service_list это dict
if not isinstance(service_list, dict):
raise
# Используйте удобопроизносимые имена.
service_list = CPSR.remove_superfluous_free_services(service_list)
# superfluous переводится как не нужный,
# но как это слово вообще произносится ???? о_О
return reduce(lambda acc, x: acc + x['sum'], service_list)non_free_invoice_services = SecurityRequest.remove_free_services(invoice_services)
BAD
Используйте удобопроизносимые имена
Maybe Better
service_list = CPSR.remove_superfluous_free_services(service_list)from model.cpsr import CPSR # Избегайте схем кодирования имен
# Избегайте мысленный преобразований:
# не заставляйте читателя мысленно преобразовывать ваши имена в другие.
from model.company import CPSR as CPreSR
# Имена должны передавать намерения программиста
def get_sum (invoice): #Выбирайте имена, удобные для поиска.
# список по английски list ну это же список всех сервисов, че?
service_list = CPreSR.get_service_models(invoice)
# Избегайте ложных ассоциаций, затемняющих смысл кода.
# service_list это dict
if not isinstance(service_list, dict):
raise
# Используйте удобопроизносимые имена.
service_list = CPSR.remove_superfluous_free_services(service_list)
# superfluous переводится как не нужный,
# но как это слово вообще произносится ???? о_О
return reduce(lambda acc, x: acc + x['sum'], service_list)BAD?
Не используйте функциональную(процедурную, ОО) парадигму там где она мешает пониманию
Maybe Better
total_sum = sum([service.get('sum', 0) for service in non_free_invoice_services])
return total_sumMaybe Better(save some functional flavor)
return reduce(lambda acc, x: acc + x['sum'], service_list)total_sum = 0
for service in non_free_invoice_services:
total_sum += service.get('sum', 0)
return total_sumfrom model.cpsr import CPSR # Избегайте схем кодирования имен
# Избегайте мысленный преобразований:
# не заставляйте читателя мысленно преобразовывать ваши имена в другие.
from model.company import CPSR as CPreSR
# Имена должны передавать намерения программиста
def get_sum (invoice): #Выбирайте имена, удобные для поиска.
# список по английски list ну это же список всех сервисов, че?
service_list = CPreSR.get_service_models(invoice)
# Избегайте ложных ассоциаций, затемняющих смысл кода.
# service_list это dict
if not isinstance(service_list, dict):
raise
# Используйте удобопроизносимые имена.
service_list = CPSR.remove_superfluous_free_services(service_list)
# superfluous переводится как не нужный,
# но как это слово вообще произносится ???? о_О
return reduce(lambda acc, x: acc + x['sum'], service_list)from model.cpsr import SecurityRequest
from model.company import CompanyServiceRecord
def get_invoice_services_price_sum(invoice):
invoice_services = CompanyServiceRecord.get_service_models(invoice)
non_free_invoice_services = SecurityRequest.remove_free_services(invoice_services)
total_sum = sum([service.get('sum', 0) for service in non_free_invoice_services])
return total_sumBAD
Maybe Better
from model.cpsr import CPSR
from model.company import CPSR as CPreSR
def get_sum (invoice):
service_list = CPreSR.get_service_models(invoice)
if not isinstance(service_list, dict):
raise
service_list = CPSR.remove_superfluous_free_services(service_list)
return reduce(lambda acc, x: acc + x['sum'], service_list)Именование классов
-
Имена классов и объектов должны представлять собой существительные и их комбинации.
-
Имя класса не должно быть глаголом.
-
Трудности в именовании класса могут сигнализировать о нарушении классом принципа SRP (Принцип единственной ответственности )
Именование функций и методов
-
Имена методов представляют собой глаголы или глагольные словосочетания.
-
Используйте имена из пространства решения. Не стесняйтесь использовать термины из области информатики, названия алгоритмов и паттернов.
-
Описывайте все, что метод выполняет.
def get_products_ids_in_region_strategy(products, region):
if isinstance(products, dict):
return get_products_ids_in_region_from_dict(products, region)
else if isinstance(products, list):
return get_products_ids_in_region_from_list(products, region)
else:
raise SomeException
Именование функций и методов
-
Определяйте конвенции именования часто используемых операций.
-
Используйте единый согласованный лексикон.
-
Дисциплинированно используйте антонимы (add/remove, increment/decrement, open/close, begin/end).
def get_products_ids_in_region_strategy(products, region):
if isinstance(products, dict):
return get_prods_ids_in_reg_dict(products, region)
else if isinstance(products, list):
return get_prods_ids_in_reg_list(products, region)
else:
raise SomeException
Именование переменных
-
Имена должны быть максимально конкретны.
-
Хорошее имя в большей степени выражает что, а не как.
-
Присваивайте булевым переменным имена, подразумевающие значение true или false.
// Имя не в кемелкейсе это ж JS
import { image_or_text } from '......somesheet';
// Глагол. Обычно это означает что можно заменить просто на набор функций
class ElemResizer {
constructor(element) {
this.element = element;
}
static newSize(elem) {
return elem.big();
}
newSize() {
if (image_or_text(this.elem) === true) { // Булевым переменным имена, подразумевающие значение true или false.
this.elem.height = this.elem.height*2;
this.elem.width = this.elem.width*2;
return this.elem;
}
return this.element.bold();
}
installNew(element) { // Дисциплинированно используйте антонимы
this.element = element;
}
terminate() { // uninstall - не remove, не purge, не delete
this.element = null;
}
}import { isImage } from '......somesheet';
const multiplyImageSize = (image_element, multiplicator) => {
if (isImage(image_element) === false) {
throw "Not an image";
}
image_element.height = this.elem.height*multiplicator;
image_element.width = this.elem.width*multiplicator;
}NEVER AGAIN!!1!!!!1!!
-
Избегайте обманчивых имен или аббревиатур(CPSR и SPQR)
-
Избегайте имен, имеющих похожие значения(regular, repeating, recurrent)
-
Избегайте переменных, имеющих разную суть, но похожие имена(sex и sex, graph(chart) и graph(math))
-
Избегайте имен, включающих цифры(Algorytm30Days)
-
Избегайте орфографических ошибок(cmopany_tag)
-
Избегайте слов, при написании которых люди часто допускают ошибки(engineer)
-
Проводите различие между именами не только по регистру букв
-
Избегайте смешения естественных языков(is_gagrin_day, big_num_kak_tvoya_mamka)
-
Избегайте имен стандартных типов, переменных и методов
-
Не используйте имена, которые совершенно не связаны с тем, что представляют переменные(lol, kek, azaza)
-
Избегайте имен, содержащих символы, которые можно спутать с другими символами(1,l,I)
-
Избегайте имен, значение которых Вы сами только с google translate поймете

NEVER AGAIN!!1!!!!1!!
-
Избегайте обманчивых имен или аббревиатур(CPSR и SPQR)
-
Избегайте имен, имеющих похожие значения(regular, repeating, recurrent)
-
Избегайте переменных, имеющих разную суть, но похожие имена(sex и sex, graph(chart) и graph(math))
-
Избегайте имен, включающих цифры(Algorytm30Days)
NEVER AGAIN!!1!!!!1!!
-
Избегайте орфографических ошибок(cmopany_tag)
-
Избегайте слов, при написании которых люди часто допускают ошибки(engineer)
-
Проводите различие между именами не только по регистру букв
-
Избегайте смешения естественных языков (is_gagrin_day, big_num_kak_tvoya_mamka)
NEVER AGAIN!!1!!!!1!!
-
Избегайте имен стандартных типов, переменных и методов
-
Не используйте имена, которые совершенно не связаны с тем, что представляют переменные(lol, kek, azaza)
-
Избегайте имен, содержащих символы, которые можно спутать с другими символами(1,l,I)
-
Избегайте имен, значение которых Вы сами только с google translate поймете
USE TOOLS
LINT:
GOLINT, JSLINT,FLAKE-8
FORMATTER:
PRETTIER, GOFMT, BLACK
Функции
Функция должна быть:
-
Компактной
-
Выполнять одну операцию
-
Выполнять действие или отвечать на вопрос
-
Все команды функции должны находится на одном уровне абстракции
async def call_data_consumer(
call_queue: Queue,
):
while call_queue.qsize():
call_data = await call_queue.get()
await send_call_data(call_data)
db_client = loop.run_until_complete(create_engine(
config['db']['dsn'],
))
async with db_client.acquire() as conn:
await conn.execute(
call_data_table.update().values(
status=CallSendStatus.SENT
).where(
call_data_table.c.id.in_(call_data)
)
)async def call_data_consumer(
call_queue: Queue,
):
while call_queue.qsize():
call_data = await call_queue.get()
await send_call_data(call_data)
await update_calls_status(call_data, CallSendStatus.SENT)
Функции
Функция должна быть(но это не точно и очень спорно):
-
Не повторять код другой функции
Функции
Дополнительные рекомендации
-
Меньше аргументов(в идеале один)
-
Меньше побочных эффектов(в идеале нет)
-
Изолируйте блоки try/except в отдельные функции
Классы
Класс должен быть:
-
Компактным
-
Отвечать приницпу SPR
-
Быть абстрактным если подразумевается его наследование
Структуры данных/Классы
В объектно ориентированном языке программирования все данные должны представляться в виде объектов( экземпляров классов).

Структуры данных/Классы
Экземпляры классов
- Скрывают данные за абстракциями
- Дают методы для работы с ними
Структуры данных
- Дают прямой доступ к данным
- Не имеют методов для работы с ними

Структуры данных/Классы
Итого
Функциональный код (код опирающийся на структуры данных) позволяет легко добавлять новые функции без изменения существующих структур данных. Объектно - ориентированный код, напротив, упрощает добавление новых классов без изменения существующих функций.
И наоборот
Функциональный код усложняет добавление новых структур данных, потому что оно требует изменения всех функций. Объектно-ориентированный код усложняет добавление новых функций(методов), потому что для этого должны измениться все классы.
Структуры данных/Классы
Проблема ужа и ежа



class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def increaseSalary(self, amount):
self.salary = self.salary + amount
def increaseSalary(employee: Employee, amount: int):
employee.salary = employee.salary+amount
employee.last_salary_increase_date = datetime.now()
save_comment(employee)
def save_comment(employee):
save(f'{employee.name} salary increased at {employee.last_salary_increase_date}')Комментарии
-
Предпочитайте выразительность кода комментариям.
-
Используйте комментарии когда только кода недостаточно.
Комментарии
-
Неточные комментарии гораздо вреднее, чем полное отсутствие комментариев.
-
Комментарии не компенсируют плохого кода
Комментарии
-
Комментарий это всегда дублирование (меняется код - меняется комментарий)
-
Используйте комментарии когда надо обьяснить почему вообще появился этот код (Юридические комментарии)
Смертные грехи
Паровозик смерти
Баянист
Ексепшин коубой
station_leader_name = paravozik.get_marshrut().get_station('Pukino').get_station_leader().get_name()sum = a[0][1][0]['sum']do_something()
res = requests.get('htpz://nonexistant_with%errors&url')
do_something_very_necessary(res)

Источники
-
Чистый код
-
Совершенный код
-
Чистая архитектура
-
Программист-прагматик
-
Архитектура корпоративных программных приложений
-
Рефакторинг. Улучшение существующего кода
-
Эффективная работа с унаследованным кодом
-
Моя жизнь
Чистый код Evo SPL 2018
By Volodymyr Selyukh
Чистый код Evo SPL 2018
- 616