|
| 1 | +Ввод/вывод vs Обработка данных |
| 2 | + |
| 3 | + |
| 4 | +Ключевой критерий качества кода — это стоимость внесения в него изменений. |
| 5 | +Если изменять программу сложно, то проект медленно развивается, несет убытки |
| 6 | +и, в конечном счете погибает. С другой стороны, бесконечно расширяемый и |
| 7 | +безгранично гибкий код — это как сферический конь в вакууме. Теоретически |
| 8 | +возможен, но практической ценности не несет. |
| 9 | + |
| 10 | +Один из часто встречающихся и оправданных приемов — это отделение обработки |
| 11 | +данных от процесса ввода/вывода. Рассмотрим несколько примеров. |
| 12 | + |
| 13 | +Пример. Подбор онлайн-курса |
| 14 | + |
| 15 | + |
| 16 | +По условию задачи нужно скачать из сети данных об онлайн-курсах, выбрать из |
| 17 | +них лучшие и сохранить результат в xlsx файл. Вот фрагмент кода: |
| 18 | + |
| 19 | +def get_courses_list(courses_url): |
| 20 | + html = fetch_html(courses_url) |
| 21 | + if html: |
| 22 | + # .... parsing logic |
| 23 | + return courses_list |
| 24 | + else: |
| 25 | + print("can't load list of courses") |
| 26 | + exit() |
| 27 | +Теперь примерим на себя роль провидца и подумаем какой функционал потребуется |
| 28 | +через месяц: |
| 29 | + |
| 30 | +В случае сетевой ошибки взять паузу в 10 секунд и повторить попытку, затем |
| 31 | +подождать еще 30 секунд и так далее. |
| 32 | +В случае если адрес недоступен - постучаться по другому url в зеркало сайта. |
| 33 | +В случае ошибки сделать запись в лог и взять данные из ранее подготовленного |
| 34 | +кеша. |
| 35 | +Как все это сделать когда def get_courses_list сама завершает программу ?! От |
| 36 | +вызова exit() надо отказаться. Можно выбросить исключение и таким образом |
| 37 | +сообщить о проблеме внешнему коду, пускай там разбираются. |
| 38 | + |
| 39 | +Вызов print тоже стоит вынести из тела функции наружу. В рассмотренных |
| 40 | +сценариях вывод в консоль зависит от общей логики загрузки данных и |
| 41 | +многократных вызовов def get_courses_list. |
| 42 | + |
| 43 | +Что еще может потребоваться в скором будущем? |
| 44 | + |
| 45 | +Отладить и покрыть тестами парсер HTML страницы. |
| 46 | +Ускорить работу скрипта, хранить ранее скачанные страницы в кеше на жестком |
| 47 | +диске. |
| 48 | +Ага, значит вызывать fetch_html() внутри def get_courses_list не такая уж |
| 49 | +хорошая идея. Жить будет легче если передать в def get_courses_list строку с |
| 50 | +HTML разметкой вместо courses_url. Вуаля, мы решили проблемы еще до их |
| 51 | +появления на горизонте! |
| 52 | + |
| 53 | +Пойдем дальше. Код другой функции: |
| 54 | + |
| 55 | +def get_course_info(html): |
| 56 | + # ... parsing logic |
| 57 | + |
| 58 | + rating = soup.find_all('div', attrs={'class': 'ratings-text'}) |
| 59 | + if rating: # check if rating is not empty list |
| 60 | + rating = rating[0].contents[0].text |
| 61 | + else: |
| 62 | + # we wanna be user-friendly, with nice output to xlsx |
| 63 | + rating = "No rating yet" |
| 64 | + |
| 65 | + # .... parsing logic |
| 66 | + |
| 67 | + return course_data |
| 68 | +Что может произойти с кодом дальше? |
| 69 | + |
| 70 | +Если рейтинга нет — надо искать его на другом сайте. |
| 71 | +В xlsx указывать не просто отсутствие рейтинга, а еще на каких сайтах искал. |
| 72 | +Отчет о курсах без рейтинга выгружать в дополнительную вкладку xlsx, чтобы |
| 73 | +удобнее было руками проверять. |
| 74 | +Для всего этого нужно уметь отличать от прочих ситуацию "рейтинг неизвестен". |
| 75 | +В Python для этих целей предусмотрено значение rating = None. А строку "No |
| 76 | +rating yet" можно переместить туда где данные подготавливаются к выводу в xlsx. |
| 77 | + |
| 78 | +Та же функция, часть вторая, последняя: |
| 79 | + |
| 80 | +def get_course_info(html): |
| 81 | + # ... more parsing logic is here |
| 82 | + |
| 83 | + # number prefix is usefull for simple sorting data before output to xlsx |
| 84 | + return { |
| 85 | + '1_title': title, |
| 86 | + '2_date': start_date, |
| 87 | + '3_language': language, |
| 88 | + '4_weeks': duration, |
| 89 | + "5_rating": rating |
| 90 | + } |
| 91 | +Сразу возникают вопросы. А если нужна еще одна выгрузка в формате csv, с |
| 92 | +другим порядком столбцов, как это сделать? Как заменить столбец 2_date на |
| 93 | +days_before_start ? |
| 94 | + |
| 95 | +Кроме того, наперед известно, что пользовательский интерфейс — будь то вывод в |
| 96 | +консоль или запись в файл — меняется очень часто. Было бы удобно собрать все, |
| 97 | +что относится к форматированию вывода в одном месте. Например, всю логику |
| 98 | +выгрузки в xlsx поместить в def fill_xlsx(workbook, courses):, а вывод в |
| 99 | +консоль собрать внутри if __name__=='__main__':. Удастся избежать вычитывания |
| 100 | +и повторной отладки всей программы от начала до конца, ведь изменения локальны |
| 101 | +и изолированы. |
| 102 | + |
| 103 | +Вместо заключения |
| 104 | + |
| 105 | + |
| 106 | +В результате мы пришли к ситуации, когда логика обработки данных слабо зависит: |
| 107 | + |
| 108 | +1)от источника данных; |
| 109 | +2)от формата вывода в файл. |
| 110 | + |
0 commit comments