django подключение второй базы данных без моделей

Может понадобится, например, когда нужно перенести какие то данные из одной базы данных, в базу данных под управлением django. В таком случае, по первой базе данных нет необходимости (или невозможно) формировать модели.

При необходимости, к первой базе тоже можно применить модели в django приложении.

Первым делом, добавляем второе подключение («sourcedb«), наряду с основным («default«) в файле конфигов settings.py. Для примера, у нас mysql:

...
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.mysql",
        "NAME": "DATABASE_NAME_default",
        "HOST": "DATABASE_HOST_default",
        "PORT": "3306",
        "USER": "DATABASE_USER_default",
        "PASSWORD": "DATABASE_PASSWORD_default",
        'OPTIONS': {
            'sql_mode': 'traditional',
        }
    },
    "sourcedb": {
        "ENGINE": "django.db.backends.mysql",
        "NAME": "DATABASE_NAME_sourcedb",
        "HOST": "DATABASE_HOST_sourcedb",
        "PORT": "3306",
        "USER": "DATABASE_USER_sourcedb",
        "PASSWORD": "DATABASE_PASSWORD_sourcedb",
        'OPTIONS': {
            'sql_mode': 'traditional',
        }
    }
}
...

Теперь можно использовать второе подключение для забора данных:

1) одну запись (fetchone):

from django.db import connections
…

def get_single_record_from_second_database(order_number):

    try:
        with connections['sourcedb'].cursor() as cursor:
            cursor.execute("""SELECT id, `sum`, user_id 
                            FROM orders 
                            WHERE order_number = '%s'
                            """ % order_number)
            order = cursor.fetchone()
            cursor.close()
    except Exception as error:
        print("Failed to read data from Orders table (for %s):" % order_number, error)
...

2) или обрабатываем много записей (fetchall)

from django.db import connections
…

def get_multiple_records_from_second_database(date):

    try:
        with connections['sourcedb'].cursor() as cursor:
            cursor.execute("""SELECT id, `sum`, user_id 
                            FROM orders 
                            WHERE date = '%s'
                            """ % date)
            orders = cursor.fetchall()
                for order in orders:
                    ...
            cursor.close()
    except Exception as error:
        print("Failed to read data from Orders table (for %s):" % date, error)
...

В цикле for можно обрабатывать заказы (order) и заполнять данными основную (default) базу данных

Django python — загрузка csv и сохранение в базу данных

Это руководство для новичков в Django, которые прочитали начальное руководство по Django на официальном сайте и столкнулись с задачей загрузки csv файла с таблицей в базу данных

В этом случае, у вас есть приложение polls, с урлами, моделями, вьюшками для классов вопросов (Question) и ответов (Choices). И база данных с сгенерированными таблицами (polls_question, polls_choice)

Для примера, будем загружать в базу данных csv файл с вопросами (Questions). 

Назовем файл questions_01a.csv и поместим туда такое содержимое:

Why?,2022-11-14 10:00
For what?,2022-11-14 10:01
When?,2022-11-14 10:02
Where?,2022-11-14 10:03

Перенос строк — как разделитель информации по вопросам, и запятая — как разделитель ячеек данных

Изменения в /polls/urls.py:

from django.urls import path

from . import views

app_name = 'polls'
urlpatterns = [
    …
    path('upload_csv/', views.upload_csv, name='upload_csv'),
]

Изменения в /polls/views.py:

from django.http import HttpResponseRedirect
from django.shortcuts import render, get_object_or_404
from django.urls import reverse
from django.views import generic
from django.utils import timezone
from django.contrib import messages

from .models import Question, Choice

…

def upload_csv(request):
    data = {}
    if "GET" == request.method:
        return render(request, "polls/upload.html", data)
    # if not GET, then proceed
    try:
        csv_file = request.FILES["csv_file"]
        if not csv_file.name.endswith('.csv'):
            messages.error(request, 'File is not a CSV')
            return HttpResponseRedirect(reverse("polls:upload_csv"))
        # if file is too large - error
        if csv_file.multiple_chunks():
            messages.error(request, "Uploaded file is too big (%.2f MB). " % (csv_file.size/(1000*1000),))
            return HttpResponseRedirect(reverse("polls:upload_csv"))

        file_data = csv_file.read().decode("utf-8")

        lines = file_data.split("\n")
        # loop over the lines and save them to db via model
        for line in lines:
            fields = line.split(",")

            try:
                question = Question(
                    question_text=fields[0],
                    pub_date=fields[1],
                )
                question.save()

            except Exception as e:
                messages.error(request, "Unable to upload file. "+repr(e))
                pass

    except Exception as e:
        messages.error(request, "Unable to upload file. "+repr(e))

    return HttpResponseRedirect(reverse("polls:upload_csv"))

Что делаем в views.py:

  • Импортируем стандартный фреймворк сообщений (messages), чтобы можно было на страничке выводить ошибки
  • Файл будем посылать методом POST, если же запрос GET — открываем страницу аплода
  • Делаем простые проверки получаемого файла (тип файла и максимальный размер)
  • Читаем построчно и сохраняем в базу данных, через модель

Создаем страничку для загрузки файла — /polls/templates/polls/upload.html со следующим кодом:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Upload</title>
    <link href="https://netdna.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <form action="{% url "polls:upload_csv" %}" method="POST" enctype="multipart/form-data" class="form-horizontal">
        {% csrf_token %}
        <div class="form-group">
            <label for="name" class="col-md-3 col-sm-3 col-xs-12 control-label">File: </label>
            <div class="col-md-8">
                <input type="file" name="csv_file" id="csv_file" required="True" class="form-control">
            </div>
        </div>
        <div class="form-group">
            <div class="col-md-3 col-sm-3 col-xs-12 col-md-offset-3" style="margin-bottom:10px;">
                 <button class="btn btn-primary"> <span class="glyphicon glyphicon-upload" style="margin-right:5px;"></span>Upload </button>
            </div>
        </div>
        {% if messages %}
        <ul class="messages">
            {% for message in messages %}
            <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
            {% endfor %}
        </ul>
        {% endif %}
    </form>
</body>
</html>

Что делаем в upload.html:

  • Минималистичная html страница
  • Подключаем стили бутстрап для более-менее симпатичного отображения элементов
  • Форма с кнопкой Загрузки
  • CSFR токен для защиты формы от размещения ее на внешних источниках, чтобы украсть ваши данные (стандартная защита)
  • Блок для отображения ошибок messages

Запускаем проект и пробуем открывать страничку 

http://127.0.0.1:8000/polls/upload_csv/

Загружаем наш заранее заготовленный файл questions_01a.csv и проверяем базу данных на наличие новых строк

Выглядит это так:

Оптимизация запросов MySQL — ускоряем запросы

  • Одна из распространённых причин медленных запросов — обработка лишних данных

Это может быть запрос лишних строк из большой таблицы, где не все они нужны. Можно использовать LIMIT или ограничивать выборку WHERE

SELECT * FROM huge_table LIMIT 10;
-- или
SELECT * FROM huge_table WHERE id BETWEEN 0 AND 1000;

Это может быть запрос всех столбцов в таблице с большим их количеством или в нескольких таблицах, соединенных JOIN. Лучше, запрашивать только необходимые столбцы

SELECT id, name, email FROM users LIMIT 1000;

Это может быть повторный запрос за теми же данными. В этом случае, если не получается избежать, стоит кешировать результаты и использовать данные из кеша

  • Другая причина медленных запрсов — анализ лишних данных

Необходимо минимизировать этот параметр. Например, запрос по индексируемому столбцу анализирует очень ограниченный набор данных, а по этому же столбцу без индекса — может анализировать все строки таблицы. Простейшее решение будет добавление или оптимизация индексов (ссылка на статью) 

Каким образом и сколько данных анализируется, можно посмотреть используя команду EXPLAIN и строка rows в результатах

  • Эффективным способом ускорения сложного запроса может быть разбиение его на более простые, которые выполняются быстрее первоначального
-- Было
SELECT * FROM tag 
JOIN tag_post ON tag_post.tag_id = tag.id
JOIN post ON tag_post.post_id = post.id
WHERE tag.tag_name = ‘mysql’;

-- Стало
SELECT * FROM tag WHERE tag_name = ‘mysql’;
SELECT * FROM tag_post WHERE tag_id = 1234;
SELECT * FROM post WHERE id IN (123, 456,789);
  • Или разбиение очень длительного запроса на более короткие порции
-- Было
DELETE FROM messages WHER created < DATE_SUB(NOW(), INTERVAL 3 MONTH);

-- Стало
DELETE FROM messages WHER created < DATE_SUB(NOW(), INTERVAL 3 MONTH) LIMIT 1000; 
-- несколько раз
  • Оптимизатор MySQL плохо оптимизирует корректированные подзапрос вида
SELECT * FROM films WHERE id IN (
    SELECT film_id FROM film_actors WHERE actor_id = 100
);

В этом случае, лучше разделить и связать их на бекенде

SELECT film_id FROM film_actors WHERE actor_id = 100;
-- И затем
SELECT * FROM films WHERE id IN (1,5,9,13,77);
  • Не стоит злоупотреблять сортировкой ORDER BY, так как, без индекса на сортируемом столбце, она может значительно увеличить время выполнения запроса. Можно использовать LIMIT для ускорения сортировки, если она необходима
  • Запросы с UNION часто идет с ограничением LIMIT, и для ускорения, следует добавить LIMIT в оба под-запроса
-- Было
(SELECT id, name FROM film_actors order by name)
UNION ALL
(SELECT id, name FROM theatre_actors order by name)
LIMIT 20;

-- Стало
(SELECT id, name FROM film_actors order by name LIMIT 20)
UNION ALL
(SELECT id, name FROM theate_actors order by name LIMIT 20)
LIMIT 20;
  • Запросы с LIMIT и OFFSET на больших таблицах плохо работают с большими OFFSET, а это часто встречается в пагинации

Например для:

SELECT * FROM users ORDER BY id LIMIT 100 OFFSET 40000;

Сервер сгенерирует  40100 строк и отбросит первые 40000

Стоит заменить на:

SELECT * FROM users WHERE id > 40000 ORDER BY id LIMIT 100;
  • Ну и на последок, на бекенде часто используется ORM для генерации запросов. И нужно понимать, что для сложных запросов ORM формирует неоптимальный медленный запрос. В таком случае, следует переписать ORM запрос на чистый SQL

Обзор Книги — Не заставляйте меня думать — Стива Круга

Стив Круг — Не заставляйте меня думать

Самое актуальное издание книги — третье. Опубликовано автором в 2013м году. Почти десять лет прошло.

Считается, что книгу нужно прочитать одной из первых специалистам по Пользовательскому опыту на веб сайтах (UX-специалисты). Материал в книге подается просто и понятно. Специалистов по пользовательскому опыту на веб-сайтах пока еще мало и не каждая компания имеет такого. Поэтому стоит ознакомиться с книгой и разработчикам. Тем более что объем книги небольшой.

Некоторые вещи из книги кажутся очевидными, но ценность в акценте на них как на важных. Основной посыл книги заложен в названии — «Не заставляйте пользователя думать». Идея кажется простой, но реализовать ее сложнее.

Мысли, которые почерпнул для себя из книги Стива Круга:

  • Со временем эволюции веба, у пользователей уже сложились представления о том, как должен выглядеть сайт. Где находится логотип, где меню, где разделы, где основная информация. Лучше размещать эти разделы так и там где пользователь привык их видеть. Не стоит удивлять пользователя и заставлять его искать привычные вещи.
  • Привычные названия для действий — это хорошо (кнопка «Регистрация»). Не привычные, остроумные, вычурные — плохо (вместо «Войти» — кнопка «Вперед за впечатлениями!»), так как заставляет пользователя задуматься и потерять время.
  • Основная навигация сайта (лого, слоган, меню, футер) и сервисные функции (войти, зарегистрироваться, поиск) должны быть на каждой странице и выглядит одинаково
  • Пользователи проводят на нашем сайте гораздо меньше времени, чем мы думаем. Редко они читают заготовленный текст полностью. Только если это не статья по интересующей его теме или «рецепт ребрышек в духовке». Пользователи хватают информацию кусочками: пару слов из целого предложения; первое предложение из абзаца.
  • Текст необходимо форматировать так, чтобы его было удобно просматривать («просматривать» отличается от «читать»). Иерархия заголовков. Короткие абзацы. Маркирование списки. Выделенные ключевые слова или понятия
  • Эффективная визуальная иерархия. Чем важнее элемент, тем он должен быть заметнее. Визуально связываем логически-связаные элементы. Часть чего-то большего оформляем вложенностью в это «большее»
  • Логические области (лого, меню, контент, баннеры, навигация, футер) должны быть четко разграничены, чтобы облегчить пользователю ориентацию на сайте
  • Кликабельные области (кнопки, ссылки, табы) должны быть выделены, привычным образом. Чтобы не было сомнения, что это кнопка или ссылка
  • Логотип сайта находится слева сверху. Рядом — слоган, которые четко объясняет куда мы попали и чего ожидать от сайта («Лучшие свежие торты Санкт-Петербурга»)
  • Хлебные крошки на сайте — это хороший тон. Если иерархия страниц больше 2х уровней.
  • Обязательно включаем поиск по сайту. Некоторые пользователи сразу же приступают к поиску, а не навигированию через меню
  • Пункты навигационного меню должны быть лаконичными, но понятными. Включать в свои разделы точно идентифицируемые области, а не пересекающиеся. Чтобы у пользователя не возникало вопроса «Бензопила» находится в разделе «Домашнее хозяйство» или «Для дачи»
  • На главной странице сделать акцент на то, от куда начать пользователю. Это или Регистрация или Покупка или Чтение лучших статей.
  • В сражении за место на главной странице между отделами (маркетинг, сео, продакт, заказчик, директор, ux-дизайнер и другие) необходимо, чтобы ответы на 4 основных вопроса легко находились. 
  1. Что это за сайт?
  2. Что я могу здесь делать?
  3. Почему я должен остаться здесь, а не где-либо еще?
  4. Какие материалы здесь есть?
  • Главная страница все еще важна. Даже если на ваш сайт попадают из поисковика и не на главную.
  • Юзабилити-тестирование очень полезная технология. Можно получить много идей для улучшения веб-сайта, пригласив сторонних людей попробовать с ним работать

Оптимизация запросов MySQL — архитектура таблиц

Когда мы говорим про оптимизацию запросов, в первую очередь, стоит оптимизировать сами типы столбцов таблицы. Лучше это делать при создании таблиц или при добавлении новых столбцов в таблицу.

  • Меньшие по размеру типы данных обычно быстрее, так как занимают меньше места на диске, в памяти и кэше процессора
  • Следует упрощать сложные типы данных, если такое возможно. 

Например, IP адреса лучше хранить в целочисленных данных, так как сравнение строковых данных работает намного медленнее. 

Даты и время рекомендуется хранить во встроенных типах данных MySQL. Из DATETIME и TIMESTAMP, последний предпочтительнее, так как требует вдвое меньше места для хранения

  • Целые числа: TINYINT (8 бит), SMALLINT (16 бит), MEDIUMINT (24 бит), INT (32 бит), BIGINT(64 бит) и диапазоны значений от -2^(N — 1) до 2^(N — 1). В зависимости от планируемого диапазона значений — выбираем более простой тип

Часто, отрицательные значений не требуются (например, авто-инкрементирующийся ID) и можно расширить диапазон в два раза благодаря атрибуту UNSIGNED.

  • Вещественные числа занимают значительно больше места.

Расчеты с данными с плавающей точкой высокозатратные и могут приводить к ошибкам на бекенде, когда «1» на самом деле берется как «0.9999987». В том числе поэтому, целесообразнее выбрать Целочисленный тип вместо Вещественного. Если нам известно, что у данных максимум 2 знака после запятой, «109.40» храним и обрабатываем как «10940»

  • Наиболее часто используемый строковый тип — VARCHAR. Используется при хранении данных переменной длины.
  • В отличии от VARCHAR, CHAR имеет фиксированную длину и лучше оптимизирован для сравнения строк с примерно одинаковой длинной (например для хранения MD5 сверток данных пользователя). 

CHAR так же эффективен для хранения часто изменяемых данных, так как не подвержен фрагментации при записи

  • Для хранения больших данных используются: TEXT — символьные данные, BLOB — двоичные данные
  • Если количество вариаций данных ограничено, то можно использовать ENUM

ENUM работает быстро, так как, по сути, хранит целое число. Недостатком ENUM будет то, что при добавление нового значения в набор, нужно будет менять схему таблицы (ALTER TABLE) или писать миграцию на бекенде

  • Лучше избегать значений NULL, если они не нужны. 

Стоит объявлять столбец как NOT NULL. MySQL тяжелее оптимизировать запросы к столбцам, допускающим NULL

Оптимизация запросов MySQL — производительные индексы

Индексы (или ключи) — это специальные структуры данных, которые использует подсистемы хранения для ускорения нахождения строк.

  • Индексы бесполезны, и, даже, вредны, если данных в таблицы мало (тысячи или меньше строк)
  • Если данных много (десятки тысяч строк и больше) и есть запрос WHERE по одному столбцу (или же сортировка ORDER BY по этому столбцу), который выполняется часто, лучше «повесить» на него простой индекс
  • Индекс хорошо работают по полному значению, диапазону значений
  • Составной индекс включает набор индексов (например, по трем столбцам A + B + C)
  • В это случае, индекс хорошо работает по полному набору проиндексированных столбцов («A + B + C»), по префиксам (левой части индекса: «A» или «A + B») и НЕ работает по пост-фиксам (правой части индекса: «C» или «B + C»). В случае необходимости поиска по «B» или «B + C», можно создать отдельный индекс — «B + C». Опять таки, индекс «B + C» будет неэффективен при поиске по C.
  • Альтернативой при существующем индексе «A + B + C» и необходимости поиска по пост-фиксу индекса, может быть включение в поиск столбца A искусственным образом:
SELECT * FROM TABLE WHERE B=2 AND C=3;
-- преобразуем в:
SELECT * FROM TABLE WHERE A="2022-10-14" AND B=2 AND C=3;
  • Так же, необходимо рассмотреть возможность смены порядка следования столбцов в запросе WHERE. Если, имеем индекс «A + B + C», при этом наш запрос выглядит так:
-- было:
SELECT * FROM TABLE WHERE A = 1 AND C = 2 AND B = 3;
-- стало:
SELECT * FROM TABLE WHERE A = 1 AND B = 3 AND C = 2;
  • Нужно понимать, что, если в запросе есть поиск по одному столбцу и сортировка по другому, хорошо подойдет составно индекс по этим столбцам и плохо будет работать отдельный индекс по одному из столбцов
  • В случае индексов на столбец строкового типа, индекс хорошо работает по полному совпадению и при поиске по префиксу (WHERE column LIKE «abc%»). При это у индекса есть ограничения, и по длинной строке поиск будет работать плохо
  • Чем меньше памяти потребляет тип столбца, тем быстрее работает и индекс. Поэтому поиск по индексированному целочисленному столбцу гораздо быстрее, чем по строковому
  • В случае длинных строк, и необходимости поиска по ним, можно ввести индексируемый столбец с хешем по длинной строке.

Хороший пример данных, это столбец с адресом веб-страниц url и поддержка столбца  c хешем этой строки — url_hash. При сохранение данных, записываем в столбец url оригинальный адрес веб-страницы, а в url_hash — хеш строки этого адреса (например кодировкой CRC32 или другой кодировкой в цифровое значение). А при поиске ведем поиск только по индексированному url_hash.

-- долгий запрос:
SELECT * FROM TABLE WHERE url = "https://long-adress-url.com";
-- заменяем на:
SELECT * FROM TABLE WHERE url_hash = CRC32("https://long-adress-url.com");
  • Другой частый случай плохо-индексируемых данных — это IP-адреса. Для них, так же есть специальный функции на бекенде, которые превращают строку IP-адреса в числовой тип для отдельного индексируемого столбца.

Конкретные функции зависят от языка программирования. Например, для PHP подойдет функция ip2long для шифровки и long2ip для расшифровки

Впечатления о книге — Первый среди равных — Майстер, Маккенна

Первый среди равных

Почему стоит прочитать книгу:

  • Книга помогает сформироваться лидеру группы. В мире программирования, лидер — это технический директор, тим-лид, сеньор в команде разработки.
  • Отвечает на множество вопросов по работе лидеров групп
  • Приведены примеры реальных ситуаций-вызовов. Какие решения предлагали лидеры известных компаний

Недостатки книги для меня, как айтишника:

  • Многие примеры ориентированы на “продажников” или менеджеров компаний, управляющих ими
  • Текст рассчитан больше на американские или западно-европейские реалии

Но это не мешает понять главное в отношении менеджеров и сотрудников

Моменты, которые я выделил бы для себя:

  • Программисты считают себя интеллектуальной элитой и не признают авторитетов. К ним нужен особый подход
  • Формальная должность менеджера работает плохо. Чтобы лидерская позиция работала эффективно, надо чтобы команда признала лидера и неформально. В этой книге много советов в этой области
  • Вам должны доверять. В вас должны видеть советника. А чтобы это произошло, нужно помогать своим подопечным. Делать это корректно и терпеливо. При этом не навязывать свою помощь. Помощь будет удачной, только если она своевременна.
  • Подчиненные должны нравиться лидеру группы. Если это не получается сразу, необходимо найти черты или способности, которые помогут. Это важный критерий при наборе на работу новых людей
  • Интересоваться жизнью коллег и делиться своей. Строить длительные доверительные отношения. Общаться и за пределами работы
  • Помогать людям понять свои перспективы и мечты, двигаться к ним
  • При этом быть “рядом” в коменты личных или профессиональных кризисов
  • Сотрудники могут допускать ошибки. Они не должны испытывать затруднения, когда рассказывают о своих упущениях. Это достигается созданием безопасной атмосферы в группе
  • Действовать в интересах не только компании, но и своих сотрудников
  • Нововведения в группу намного лучше интегрируются если к ним приходят совместно группой
  • Одобрение — один из самых эффективных форм поощрения. Оно должно быть своевременным, искренним

Наставничество можно описать моделью:

  • Спросите как дела
  • Убедитесь, что человек готов принять наставничество
  • Задайте вопросы и предложите свою поддержку
  • Поделитесь нужной информацией
  • Активно слушайте
  • Помогите найти возможные направления действия
  • Договоритесь о следующем шаге

Модель Помощи отстающим коллегам:

  • Организуем встречу для обсуждения проблем с производительностью
  • Убеждаем сотрудника, чтобы в него верите и хотите оказать поддержку
  • Добиваемся того, что проблема существует и обсуждаем причины
  • Находим идеи для улучшения
  • Договариваемся о конкретных действиях для решения проблемы
  • Устанавливаем конкретную дату для обсуждения того, как идет исправление 

Модель противодействия Проблемному поведению:

  • Организовываем встречу
  • Описываем конкретные ситуации, иллюстрирующие проблемное поведение
  • Объясняем, почему такое поведение беспокоит вас, и выражаем желание изменить его
  • Выслушиваем объяснение коллеги по поводу причин происходящего
  • Помогаем увидеть, как изменение поведения поможет карьере сотрудника
  • Спрашиваем, какие у него есть идеи по поводу решения проблемы
  • Предлагаем свою поддержку и помощь
  • Договариваемся о плане действий и назначен дату для обсуждения хода дел

Чистый код — избавляемся от грязного кода

Чистый код — меньше багов
  • Среда разработки помогает быстрее писать код. А еще, она подсказывает как сделать код чище. Стоит обращать внимание на все предупреждения среды разработки и учитывать их. Так же выявляется и дублирование кода
  • Дублирование — большая помеха чистоте кода. Простейшая форма дублирование — целые куски дублирующего кода. Их стоит выделять в отдельные функции, иногда — классы и пере-использовать. Более сложные примеры дублирования проявляются в одинаковых условиях if-else. Вместо них следует использовать принцип полиморфизма. Существуют еще более сложные случаи дублирования. Схожие алгоритмы могут соседствовать со специфичной логикой. Используем паттерны проектирования, например Шаблонный метод или Стратегия.
  • Дублирующий код может быть инкапсулирован в более высокие уровни абстракций, если необходимо пере-использовать его в других классах.
  • Не используемый код следует удалять, а не сохранять на будущее. Тоже происходит с “мертвым кодом”, до которого алгоритм никогда не дойдет. Это нередкая “болезнь” циклов if-else и switch-case
  • Вертикальное разделение помогает чтению кода. Переносы строк, без пропуска строк, покажут что код относится к одной логике. Пропуски строк разделят алгоритм на 2е логически-обособленные части
  • Такой балласт, как пустой конструктор или пустое тело цикла будет мешать читать код. Следует удалить их.
  • Пояснительные переменные помогают чтению и пониманию кода
$today = date('Y-m-d', now());
$targetDateString = '2020-07-08';
$targetDate = date('Y-m-d', strtotime($targetDateString));
If ($today >= $targetDate) {
	doTheJob();
}
  • Чистоте кода, так же, поможет соблюдение стандартных конвенций. Не обязательно это должен быть согласованный документ. Сам код служит примером оформления.
  • Так называемые “волшебные числа” заменяют именованными константами — пример кода со стр 339
$dailyPay = $hourlyPay * 8;
$PI = 3.14;
$circleLength = $radius * $PI * 2;

Конфигурационные данные выносим на высокие уровни абстракций — конфигурационные файлы или файл

Чистый код — комментарии

Чистый код — искусство комментирования

Комментарии

Комментарии к коду — не однозначная концепция. С одной стороны, комментарии используют для пояснения работы следующего кода. С другой стороны, одна из концепций “Чистого кода”: код должен быть понятным и без комментариев.

От сюда, обозначим основные концепции:

  • Комментарии быстро устаревают. Важно не забывать обновлять комментарии при обновлении следующего кода. Это известная проблема комментариев. Устаревшие комментарии могут быть вредны. Они описывают одно поведение, а алгоритм кода — другое, что путает программиста.
  • Комментарии увеличивают размер кодовой базы и время на изучение кода. Нужно прочитать не только код, но и комментарии, чтобы вникнуть в алгоритм. 
  • Есть такое заблуждение, что комментарии компенсируют плохой код. Например, пишем не очень понятный или запутанный код, а далее, в комментариях поясняем его работу. Лучше написать ясный и выразительный код, чтобы комментарии не потребовались
  • Замена комментариям — подходящие, говорящие имена переменных, методов и классов
  • Лучше удалять старый код, чем комментировать его. Закомментированный код со временем разрастается и создает большой объем бесполезного или вредного груза. 
  • В некоторых случаях комментарии уместны. Они будут мощным инструментом. 

Уместные типы комментариев:

  • Юридические комментарии в начале модулей или классов, копирайты
  • Пояснения к сложным регулярным выражениям или маскам
  • Если используются какие то стандартные имена из сторонних библиотек. При этом, мы не можем на них повлиять, но можем описать комментариями
  • Когда передают опыт использования или предупреждения (например, // код ниже может работать очень долго если количество записей в базе данных больше миллиона)
  • Комментарии TODO
  • Усиление значения какого то факта, если его значение может показаться неочевидным (например, //не удаляйте код ниже, так как это защита от…)
  • Комментарии вида APIdoc, необходимые для генерации авто-документации по методам АПИ

Чистый код — функции и классы

Чистые функции и классы

Классы и функции — важнейшие блоки построение кода проекта. Это делает их основными целями при работе с чистотой кода.

Рекомендации к написанию функций:

  • Функции должны быть как можно компактнее. Можно ориентироваться на размер максимум 20 строк, чтобы понимать масштабы
  • Функции могут содержать вложенности (циклы foreach, if else, while). Следует уменьшить число вложенностей до 1-2. В идеале одна вложенность. Больший уровень вложенности можно помещать в отдельные функции
  • Функция должна выполнять одну операцию. Это упрощает разделения алгоритма на функции
  • Если в функции есть “секции” (например объявление, инициализация, сбор данных и т.д.) значит она выполняет несколько операций. Следует разделить на разные функции
  • При этом имя функции следует выбирать таким образом, чтобы доходчиво объяснить операцию
  • Связанные функции следует размещать ближе к “родительским”. Создаем, таким образом, понятный уровень иерархии, который читается сверху вниз
  • Следует минимизировать количество аргументов функций. Большое количество аргументов усложняет понимание функции. Лучше всего читаются функции без аргументов. Они могут манипулировать полями классов ($this->variable). 
  • Если в функцию нужно передавать много аргументов, следует рассмотреть объединение их в класс, тогда из аргументов будет только один объект класса

Классы:

  • Оптимальный вариант строения класса: сначала идут открытые переменные, затем приватные. За списком переменных идут публичные функции. За каждой публичной функцией следуют приватные вспомогательные функции. При таком размещении, класс читается как газетная статья
  • Классы должны быть компактными. Размер класса может определить принцип единой ответственности. Этот принцип говорит, что класс должен иметь только одну причину для изменения. Пример класса с 2мя причинами для изменения ниже
Class UserDashboard {
    public function getLastFocusedElement() {…};
    public function setLastFocusedElement() {…};
    public function getMajorVersion() {…};
    public function getMinorVersion() {…};
}

Его стоит разделить на 2 класса: первый  — для работы с выделенным элементом дашборда, второй — для версионирования дашборда

  • Следует стремиться к бОльшей связанности в классах. БОльшая связанность достигается, когда бОльшая часть методов класса использует бОльшую часть переменных экземпляра класса. Разделение большого метода на серию более мелких снижает связанность в классе. После этого становится понятнее как разбить исходный класс на более компактные. В результате, повышается связанность и чистота классов, полученных из исходного класса.