diff --git a/README.md b/README.md index d2ec80c6..4c072eb9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -Отделяйте ввод/вывод от обработки - +# Отделяйте ввод/вывод от обработки Ключевой критерий качества кода — это стоимость внесения в него изменений. Если изменять программу сложно, то проект медленно развивается, несет убытки @@ -7,80 +6,88 @@ безгранично гибкий код — это как сферический конь в вакууме. Теоретически возможен, но практической ценности не несет. +Если мы заранее будем знать как изменятся требования к коду через год, два или +десять лет, то уже сейчас можно заложить необходимые механизмы расширения +функциональности. К сожалению, такие сведения сложно получить, а зачастую +просто невозможно. Приходится опираться на свой опыт, опыт товарищей, +прорабатывать разные сценарии развития проекта, подстраховываться. + Один из часто встречающихся и оправданных приемов — это отделение обработки данных от процесса ввода/вывода. Рассмотрим несколько примеров. -Пример. Подбор онлайн-курса - +## Пример 1. Подбор онлайн-курса По условию задачи нужно скачать из сети данные об онлайн-курсах, выбрать из них лучшие и сохранить результат в xlsx файл. Вот фрагмент кода: - +```py def get_courses_list(courses_url): html = fetch_html(courses_url) if html: - # .... parsing logic + # ... parsing logic return courses_list else: print("can't load list of courses") exit() +``` Теперь примерим на себя роль провидца и подумаем какой функционал потребуется через месяц: -В случае сетевой ошибки взять паузу в 10 секунд и повторить попытку, затем +1. В случае сетевой ошибки взять паузу в 10 секунд и повторить попытку, затем подождать еще 30 секунд и так далее. -В случае если адрес недоступен - постучаться по другому url в зеркало сайта. -В случае ошибки сделать запись в лог и взять данные из ранее подготовленного +2. В случае если адрес недоступен - постучаться по другому url в зеркало сайта. +3. В случае ошибки сделать запись в лог и взять данные из ранее подготовленного кеша. -Как все это сделать когда def get_courses_list сама завершает программу ?! От -вызова exit() надо отказаться. Можно выбросить исключение и таким образом + +Как все это сделать когда `get_courses_list` сама завершает программу? От +вызова `exit` надо отказаться. Можно выбросить исключение и таким образом сообщить о проблеме внешнему коду, пускай там разбираются. -Вызов print тоже стоит вынести из тела функции наружу. В рассмотренных +Вызов `print` тоже стоит вынести из тела функции наружу. В рассмотренных сценариях вывод в консоль зависит от общей логики загрузки данных и -многократных вызовов def get_courses_list. +многократных вызовов `get_courses_list`. Что еще может потребоваться в скором будущем? -Отладить и покрыть тестами парсер HTML страницы. -Ускорить работу скрипта, хранить ранее скачанные страницы в кеше на жестком +1. Отладить и покрыть тестами парсер HTML страницы. +2. Ускорить работу скрипта, хранить ранее скачанные страницы в кеше на жестком диске. -Ага, значит вызывать fetch_html() внутри def get_courses_list не такая уж -хорошая идея. Жить будет легче если передать в def get_courses_list строку с -HTML разметкой вместо courses_url. Вуаля, мы решили проблемы еще до их + +Ага, значит вызывать `fetch_html` внутри `get_courses_list` не такая уж +хорошая идея. Жить будет легче если передать в `get_courses_list` строку с +HTML разметкой вместо `courses_url`. Вуаля, мы решили проблемы еще до их появления на горизонте! Пойдем дальше. Код другой функции: - +```py def get_course_info(html): - # ... parsing logic - + # ... parsing logic + rating = soup.find_all('div', attrs={'class': 'ratings-text'}) - if rating: # check if rating is not empty list + if rating: rating = rating[0].contents[0].text else: - # we wanna be user-friendly, with nice output to xlsx rating = "No rating yet" - - # .... parsing logic + + # ... parsing logic return course_data +``` Что может произойти с кодом дальше? -Если рейтинга нет — надо искать его на другом сайте. -В xlsx указывать не просто отсутствие рейтинга, а еще на каких сайтах искал. -Отчет о курсах без рейтинга выгружать в дополнительную вкладку xlsx, чтобы +1. Если рейтинга нет — надо искать его на другом сайте. +2. В xlsx указывать не просто отсутствие рейтинга, а еще на каких сайтах искал. +3. Отчет о курсах без рейтинга выгружать в дополнительную вкладку xlsx, чтобы удобнее было руками проверять. + Для всего этого нужно уметь отличать от прочих ситуацию "рейтинг неизвестен". -В Python для этих целей предусмотрено значение rating = None. А строку "No +В Python для этих целей предусмотрено значение `rating = None`. А строку "No rating yet" можно переместить туда где данные подготавливаются к выводу в xlsx. Та же функция, часть вторая, последняя: - +```py def get_course_info(html): - # ... more parsing logic is here + # ... more parsing logic - # number prefix is usefull for simple sorting data before output to xlsx return { '1_title': title, '2_date': start_date, @@ -88,23 +95,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 поместить в `fill_xlsx(workbook, courses)`, а вывод в +консоль собрать внутри main. Удастся избежать вычитывания и повторной отладки всей программы от начала до конца, ведь изменения локальны и изолированы. -Вместо заключения - +## Вместо заключения В результате мы пришли к ситуации, когда логика обработки данных слабо зависит: -1)от источника данных; -2)от формата вывода в файл. +1. от источника данных; +2. от формата вывода в файл. + +![image](https://dvmn.org/filer/canonical/1594117412/678/) +Кроме того, часть кода удалось превратить в [чистые функции](https://devman.org/encyclopedia/decomposition/decomposition_pure_functions/), что облегчит +тестирование и повторное использование. +Стратегия по отделению операций ввода/вывода от обработки данных встречается +повсеместно, в самых разных программах: от небольших скриптов до серьезных и +крупных проектов. Это один из базовых приемов, нужно уверенно им владеть.