Чистый код

Владимир Селюх

Разработчик ПО

Что мы сегодня обсудим?

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_sum

Maybe 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_sum
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)
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_sum

BAD

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