Skip to content
Open
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e9fb5ff
Добавил поясняющий текст
MartinRogan Oct 17, 2018
52a1d2b
Добавил ссылку
MartinRogan Oct 17, 2018
03d2330
Update README.md
dvmn-tasks Apr 28, 2022
20ead2f
fix title
keinen87 Mar 12, 2024
9e8a041
Merge pull request #698 from keinen87/patch-1
dvmn-tasks Mar 25, 2024
3f56e95
Merge branch 'pictures'
facciamoadesso Jul 9, 2025
ad37d5e
Create Поправила заголовку
facciamoadesso Jul 11, 2025
2992f42
Create Поправила заголовку.md
facciamoadesso Jul 11, 2025
0f77887
Update Поправила заголовку
facciamoadesso Jul 11, 2025
95e93cd
Merge branch 'urls' into master
facciamoadesso Jul 15, 2025
cdd06d7
Update README.md
facciamoadesso Jul 15, 2025
afc1faa
Update README.md
facciamoadesso Jul 15, 2025
a009cdf
Update README.md
facciamoadesso Jul 15, 2025
26c6ebf
Update README.md
facciamoadesso Jul 15, 2025
bb15215
Merge pull request #1 from facciamoadesso/markdown
facciamoadesso Jul 15, 2025
0d95426
Update README.md
facciamoadesso Jul 15, 2025
e59dab3
Merge pull request #2 from facciamoadesso/markdown
facciamoadesso Jul 15, 2025
659a974
Удалила ненужный коммит
facciamoadesso Jul 18, 2025
883a473
Удалила ненужный коммит
facciamoadesso Jul 18, 2025
154c52e
Исправила абзацы
facciamoadesso Jul 18, 2025
4e763f0
Исправила пропущенные абзацы
facciamoadesso Jul 18, 2025
6c82598
Исправила замечания ревьюера по markdown
facciamoadesso Jul 20, 2025
8c57ab4
Обновила подсветку синтаксиса
facciamoadesso Jul 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 49 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Ввод/вывод vs Обработка данных
# Отделяйте ввод/вывод от обработки


Ключевой критерий качества кода — это стоимость внесения в него изменений.
Expand All @@ -7,15 +7,22 @@
безгранично гибкий код — это как сферический конь в вакууме. Теоретически
возможен, но практической ценности не несет.

Если мы заранее будем знать как изменятся требования к коду через год, два или
десять лет, то уже сейчас можно заложить необходимые механизмы расширения
функциональности. К сожалению, такие сведения сложно получить, а зачастую
просто невозможно. Приходится опираться на свой опыт, опыт товарищей,
прорабатывать разные сценарии развития проекта, подстраховываться.

Один из часто встречающихся и оправданных приемов — это отделение обработки
данных от процесса ввода/вывода. Рассмотрим несколько примеров.

Пример. Подбор онлайн-курса
## Пример. Подбор онлайн-курса


По условию задачи нужно скачать из сети данных об онлайн-курсах, выбрать из
По условию задачи нужно скачать из сети данные об онлайн-курсах, выбрать из
них лучшие и сохранить результат в xlsx файл. Вот фрагмент кода:

```python
def get_courses_list(courses_url):
html = fetch_html(courses_url)
if html:
Expand All @@ -24,34 +31,38 @@ 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

Expand All @@ -65,18 +76,21 @@ 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

Expand All @@ -88,24 +102,33 @@ 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. от формата вывода в файл.


![image](https://dvmn.org/filer/canonical/1594117412/678/)
=======
Кроме того, часть кода удалось превратить в [чистые функции](https://devman.org/encyclopedia/decomposition/decomposition_pure_functions/), что облегчит
тестирование и повторное использование.

Стратегия по отделению операций ввода/вывода от обработки данных встречается
повсеместно, в самых разных программах: от небольших скриптов до серьезных и
крупных проектов. Это один из базовых приемов, нужно уверенно им владеть.