diff --git a/README.md b/README.md
index d2ec80c6..e5d6a455 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-Отделяйте ввод/вывод от обработки
+# Отделяйте ввод/вывод от обработки
Ключевой критерий качества кода — это стоимость внесения в него изменений.
@@ -7,15 +7,22 @@
безгранично гибкий код — это как сферический конь в вакууме. Теоретически
возможен, но практической ценности не несет.
+Если мы заранее будем знать как изменятся требования к коду через год, два или
+десять лет, то уже сейчас можно заложить необходимые механизмы расширения
+функциональности. К сожалению, такие сведения сложно получить, а зачастую
+просто невозможно. Приходится опираться на свой опыт, опыт товарищей,
+прорабатывать разные сценарии развития проекта, подстраховываться.
+
Один из часто встречающихся и оправданных приемов — это отделение обработки
данных от процесса ввода/вывода. Рассмотрим несколько примеров.
-Пример. Подбор онлайн-курса
+## Пример. Подбор онлайн-курса
По условию задачи нужно скачать из сети данные об онлайн-курсах, выбрать из
них лучшие и сохранить результат в xlsx файл. Вот фрагмент кода:
+```python
def get_courses_list(courses_url):
html = fetch_html(courses_url)
if html:
@@ -24,34 +31,45 @@ def get_courses_list(courses_url):
else:
print("can't load list of courses")
exit()
+```
Теперь примерим на себя роль провидца и подумаем какой функционал потребуется
через месяц:
-В случае сетевой ошибки взять паузу в 10 секунд и повторить попытку, затем
+1. В случае сетевой ошибки взять паузу в 10 секунд и повторить попытку, затем
подождать еще 30 секунд и так далее.
-В случае если адрес недоступен - постучаться по другому url в зеркало сайта.
-В случае ошибки сделать запись в лог и взять данные из ранее подготовленного
+2. В случае если адрес недоступен - постучаться по другому url в зеркало сайта.
+3. В случае ошибки сделать запись в лог и взять данные из ранее подготовленного
кеша.
-Как все это сделать когда def get_courses_list сама завершает программу ?! От
-вызова exit() надо отказаться. Можно выбросить исключение и таким образом
+
+
+
+Как все это сделать когда `def get_courses_list` сама завершает программу ?! От
+вызова `exit()` надо отказаться. Можно выбросить исключение и таким образом
сообщить о проблеме внешнему коду, пускай там разбираются.
-Вызов print тоже стоит вынести из тела функции наружу. В рассмотренных
+
+Вызов `print` тоже стоит вынести из тела функции наружу. В рассмотренных
сценариях вывод в консоль зависит от общей логики загрузки данных и
-многократных вызовов def get_courses_list.
+многократных вызовов `def get_courses_list.`
Что еще может потребоваться в скором будущем?
-Отладить и покрыть тестами парсер HTML страницы.
-Ускорить работу скрипта, хранить ранее скачанные страницы в кеше на жестком
+1. Отладить и покрыть тестами парсер HTML страницы.
+2. Ускорить работу скрипта, хранить ранее скачанные страницы в кеше на жестком
диске.
-Ага, значит вызывать fetch_html() внутри def get_courses_list не такая уж
-хорошая идея. Жить будет легче если передать в def get_courses_list строку с
-HTML разметкой вместо courses_url. Вуаля, мы решили проблемы еще до их
+
+
+
+Ага, значит вызывать `fetch_html()` внутри `def get_courses_list` не такая уж
+хорошая идея. Жить будет легче если передать в `def get_courses_list` строку с
+HTML разметкой вместо `courses_url.` Вуаля, мы решили проблемы еще до их
появления на горизонте!
+
+
Пойдем дальше. Код другой функции:
+```python
def get_course_info(html):
# ... parsing logic
@@ -65,18 +83,22 @@ def get_course_info(html):
# .... parsing logic
return course_data
+```
Что может произойти с кодом дальше?
-Если рейтинга нет — надо искать его на другом сайте.
-В xlsx указывать не просто отсутствие рейтинга, а еще на каких сайтах искал.
-Отчет о курсах без рейтинга выгружать в дополнительную вкладку xlsx, чтобы
+1. Если рейтинга нет — надо искать его на другом сайте.
+2. В xlsx указывать не просто отсутствие рейтинга, а еще на каких сайтах искал.
+3. Отчет о курсах без рейтинга выгружать в дополнительную вкладку xlsx, чтобы
удобнее было руками проверять.
+
+
+
Для всего этого нужно уметь отличать от прочих ситуацию "рейтинг неизвестен".
-В Python для этих целей предусмотрено значение rating = None. А строку "No
+В Python для этих целей предусмотрено значение `rating = None.` А строку "No
rating yet" можно переместить туда где данные подготавливаются к выводу в xlsx.
-
Та же функция, часть вторая, последняя:
+```python
def get_course_info(html):
# ... more parsing logic is here
@@ -88,23 +110,30 @@ def get_course_info(html):
'4_weeks': duration,
"5_rating": rating
}
+```
Сразу возникают вопросы. А если нужна еще одна выгрузка в формате csv, с
-другим порядком столбцов, как это сделать? Как заменить столбец 2_date на
-days_before_start ?
+другим порядком столбцов, как это сделать? Как заменить столбец `2_date` на
+`days_before_start` ?
Кроме того, наперед известно, что пользовательский интерфейс — будь то вывод в
консоль или запись в файл — меняется очень часто. Было бы удобно собрать все,
что относится к форматированию вывода в одном месте. Например, всю логику
-выгрузки в xlsx поместить в def fill_xlsx(workbook, courses):, а вывод в
-консоль собрать внутри if __name__=='__main__':. Удастся избежать вычитывания
+выгрузки в xlsx поместить в `def fill_xlsx(workbook, courses):`, а вывод в
+консоль собрать внутри `if __name__=='__main__':`. Удастся избежать вычитывания
и повторной отладки всей программы от начала до конца, ведь изменения локальны
и изолированы.
-Вместо заключения
-
+## Вместо заключения
В результате мы пришли к ситуации, когда логика обработки данных слабо зависит:
+1. от источника данных;
+2. от формата вывода в файл.
+
+
-1)от источника данных;
-2)от формата вывода в файл.
+Кроме того, часть кода удалось превратить в [чистые функции](https://dvmn.org/encyclopedia/decomposition/decomposition_pure_functions/),
+что облегчает тестирование и повторное использование.
+Стратегия по отделению операций ввода/вывода от обработки данных встречается
+повсеместно, в самых разных программах: от небольших скриптов до серьезных и
+крупных проектов. Это один из базовых приемов, нужно уверенно им владеть.