Картинки на выставку (часть 2)
При выполнении домашнего задания вы скорее всего столкнулись с проблемой изменений своего же собственного кода. На этом занятии вам предстоит научиться делать код более понятным и «гибким» — удобным для внесения изменений.
Рефакторинг
Рефа́кторинг (англ. refactoring) или перепроектирование, переработка кода — это процесс изменения внутренней структуры программы, не затрагивающий её внешнего поведения и имеющий целью облегчить понимание её работы.
В основе рефакторинга лежит последовательность небольших эквивалентных (то есть сохраняющих поведение) преобразований. Поскольку каждое преобразование маленькое, программисту легче проследить за его правильностью, и в то же время вся последовательность может привести к существенной перестройке программы и улучшению её согласованности и чёткости.
Малыми изменениями сложно изменить принципиальную архитектуру программы. Иногда рефакторинг запутанного проекта сделать так сложно, что проще выбросить написанную программу и написать её заново. Поэтому важно изначально хорошо спроектировать архитектуру программного продукта.
Парадигма структурного программирования. Проектирование сверху-вниз
Что же такое структурное программирование? Это − парадигма разработки программ с помощью представления их в виде иерархической структуры блоков. Идея структурного программирования появилась в 1970-х годах у учёного Эдсгера Вибе Дейкстры и была популяризована Никлаусом Виртом, создателем широко известного в школах языка Pascal.
В эту парадигму входит всего три пункта:
- Любая программа состоит из трёх типов конструкций: 1. последовательное исполнение; 2. ветвление; 3. цикл.
- Логически целостные фрагменты программы оформляются в виде подпрограмм. В тексте основной программы вставляется инструкция вызова подпрограммы. После окончания подпрограммы исполнение продолжается с инструкции, следующей за командой вызова подпрограммы.
- Разработка программы ведётся поэтапно методом «сверху вниз».
Первый пункт важен скорее не тем, что в нём есть, а тем, чего в нём нет: в нём нет оператора безусловного перехода goto. Именно это отличает структурное программирование от процедурного (процедурное программирование − синоним императивного).
Благодаря пункту два в языках высокого уровня появились новые синтаксические конструкции − функции и процедуры.
Пункт три − самый важный, и он является сутью парадигмы структурного программирования. Чтобы лучше понять, что представляет собой метод «сверху вниз», рассмотрим конкретный пример. Предположим, наша задача состоит в том, чтобы нарисовать на экране зайчика. Воспользуемся уже имеющейся у нас заготовкой программы с использованием pygame :
import pygame from pygame.draw import * pygame.init() FPS = 30 screen = pygame.display.set_mode((400, 400)) # Здесь мы будем рисовать pygame.display.update() clock = pygame.time.Clock() finished = False while not finished: clock.tick(FPS) for event in pygame.event.get(): if event.type == pygame.QUIT: finished = True pygame.quit()
В дальнейшем мы не будем воспроизводить весь этот код, а будем работать только над центральной частью. Что ж, давайте ее напишем:
Вуаля! Программа готова. Да, жаль только, что у нас нет такой функции, поэтому программа не работает. Что ж, придется ее написать. Но прежде чем писать функцию, нужно продумать ее интерфейс.
Проработка интерфейсов функций
Интерфейс функции — это описание того, как функция взаимодействует с окружением: какие параметры принимает и какой результат выдает (речь идет не только о возращаемом значении, но и о действиях, которые функция совершает «вовне» — например, как в нашем случае, вывод на экран изображения). Интерфейс задает стандарт, благодаря которому мы можем данной функцией пользоваться, даже не зная о том, как именно она устроена внутри.
В идеале (впрочем, на практике это не всегда удается) интерфейс функции нужно задать один раз и в дальнейшем не менять. Изменение внутренней логики работы функции без изменения ее интерфейса будет практически незаметным, а вот изменение интерфейса, который уже используется в разных частях программы, может быть довольно болезненным.
Поэтому стоит сразу сделать интерфейс функции достаточно гибким, чтобы он обеспечивал достаточную степень универсальности, но при этом не слишком сложным.
Итак, давайте подумаем над тем, какие параметры рисования зайца нам стоит задавать при вызове функции. Очевидно, что мы должны, как минимум сообщить функции, где рисовать зайца и какого он будет размера. На случай, если мы вдруг захотим рисовать зайцев разных цветов, зададим еще и цвет:
draw_hare(surface, x, y, width, height, color)
Подождите, а что значат эти x и y? Это координаты центра зайца или, может быть, кончика левого уха? В каком формате нужно задавать color? Все это должно быть где-то описано.
Документация функций
Интерфейс функции — какие параметры она принимает и что делает — следует описать в документации. В Python есть удобный механизм работы с документацией — документ-строки. В действительности это всего лишь строка в кавычках (обычно эти строки берут в тройные кавычки), записанная в начале функции:
def draw_hare(surface, x, y, width, height, color): ''' Рисует зайца на экране. surface - объект pygame.Surface x, y - координаты левого верхнего угла изображения width, height - ширина и высота изобажения color - цвет, заданный в формате, подходящем для pygame.Color '''
При создании функции ее документ-строка будет сохранена в специальное поле и будет доступна, например, при вызове функции help :
Теперь мы можем прописать вызов функции со всеми нужными параметрами:
draw_hare(screen, 200, 200, 200, 400, (200, 200, 200))
И вот теперь мы можем приступить к написанию самой функции:
def draw_hare(surface, x, y, width, height, color): draw_body() draw_head() draw_ear() draw_ear() draw_leg() draw_leg()
Аналогично мы должны продумать и интерфейсы функций для рисования отдельных частей зайца. В данном случае представляется разумным, что они должны принимать примерно тот же набор параметров, что и функция draw_hare :
def draw_body(surface, x, y, width, height, color): ''' Рисует тело зайца. surface - объект pygame.Surface x, y - координаты центра изображения width, height - ширина и высота изобажения color - цвет, заданный в формате, подходящем для pygame.Color ''' pass def draw_head(surface, x, y, size, color): ''' Рисует голову зайца. surface - объект pygame.Surface x, y - координаты центра изображения size - диаметр головы color - цвет, заданный в формате, подходящем для pygame.Color ''' pass def draw_ear(surface, x, y, width, height, color): ''' Рисует ухо зайца. surface - объект pygame.Surface x, y - координаты центра изображения width, height - ширина и высота изобажения color - цвет, заданный в формате, подходящем для pygame.Color ''' pass def draw_leg(surface, x, y, width, height, color): ''' Рисует ногу зайца. surface - объект pygame.Surface x, y - координаты центра изображения width, height - ширина и высота изобажения color - цвет, заданный в формате, подходящем для pygame.Color ''' pass
Теперь можно закончить функцию draw_hare :
def draw_hare(surface, x, y, width, height, color): ''' Рисует зайца на экране. surface - объект pygame.Surface x, y - координаты центра изображения width, height - ширина и высота изобажения color - цвет, заданный в формате, подходящем для pygame.Color ''' body_width = width // 2 body_height = height // 2 body_y = y + body_height // 2 draw_body(surface, x, body_y, body_width, body_height, color) head_size = height // 4 draw_head(surface, x, y - head_size // 2, head_size, color) ear_height = height // 3 ear_y = y - height // 2 + ear_height // 2 for ear_x in (x - head_size // 4, x + head_size // 4): draw_ear(surface, ear_x, ear_y, width // 8, ear_height, color) leg_height = height // 16 leg_y = y + height // 2 - leg_height // 2 for leg_x in (x - width // 4, x + width // 4): draw_leg(surface, leg_x, leg_y, width // 4, leg_height, color)
Функции рисования отдельных частей зайца можно пока сделать совсем простыми (сделать их более красивыми можно будет позже):
def draw_body(surface, x, y, width, height, color): ''' Рисует тело зайца. surface - объект pygame.Surface x, y - координаты центра изображения width, height - ширина и высота изобажения color - цвет, заданный в формате, подходящем для pygame.Color ''' ellipse(surface, color, (x - width // 2, y - height // 2, width, height)) def draw_head(surface, x, y, size, color): ''' Рисует голову зайца. surface - объект pygame.Surface x, y - координаты центра изображения size - диаметр головы color - цвет, заданный в формате, подходящем для pygame.Color ''' circle(surface, color, (x, y), size // 2) def draw_ear(surface, x, y, width, height, color): ''' Рисует ухо зайца. surface - объект pygame.Surface x, y - координаты центра изображения width, height - ширина и высота изобажения color - цвет, заданный в формате, подходящем для pygame.Color ''' ellipse(surface, color, (x - width // 2, y - height // 2, width, height)) def draw_leg(surface, x, y, width, height, color): ''' Рисует ногу зайца. surface - объект pygame.Surface x, y - координаты центра изображения width, height - ширина и высота изобажения color - цвет, заданный в формате, подходящем для pygame.Color ''' ellipse(surface, color, (x - width // 2, y - height // 2, width, height))
Задание недели
Сделайте себе fork проекта, который даст вам преподаватель (это проект одного из ваших товарищей).
Ваша задача сделать рефакторинг этого кода так, чтобы можно было быстро вносить изменения (местоположения объектов, количество, их размер).
Сайт построен с использованием Pelican. За основу оформления взята тема от Smashing Magazine. Исходные тексты программ, приведённые на этом сайте, распространяются под лицензией GPLv3, все остальные материалы сайта распространяются под лицензией CC-BY.
2.1. Теория¶
Язык Python является одним из самых простых в изучении и самых приятных в использовании из языков программирования, получивших широкое распространение. Программный код на языке Python легко читать и писать, и, будучи лаконичным, он не выглядит загадочным. Python — очень выразительный язык, позволяющий уместить приложение в меньшее количество строк, чем на это потребовалось бы в других языках, таких как C++ или Java.
2.1.1. История создания¶
Разработка языка Python была начата в конце 1980-х годов сотрудником голландского института CWI (Центр математики и информатики, голл. Centrum Wiskunde & Informatica) Гвидо ван Россумом (англ. Guido van Rossum), на основе языка ABC (Рисунок 2.1.1). В феврале 1991 года Гвидо опубликовал исходный текст в группе новостей alt.sources.
Рисунок 2.1.1 — Гвидо ван Россум на конференции в 2006 г. ¶
О создании Python Гвидо ван Россум написал в 1996 г.:
«Около 6 лет назад, в декабре 1989 г., я думал над проектом в области программирования в качестве хобби, которое бы заняло меня на время Рождественских выходных. Мой офис… был закрыл, но компьютер был и дома, и, в общем, это все, что у меня было на руках. Я решил написать интерпретатор для нового скриптового языка, о котором думал в последнее время: наследника ABC, который бы оказался привлекательным для Unix/C хакеров. Пилотное название «Питон» было выбрано с одной стороны в связи с немного скептическим отношением к перспективам проекта, а с другой — из-за того, что я большой фанат шоу «Летающий цирк Монти Пайтона».
Покинув в декабре 2012 года корпорацию Google, с 2013 года Гвидо работал в компании Dropbox Inc, выйдя на пенсию в 2019, а с 2020 работает в компании Microsoft. Имея статус «великодушного пожизненного диктатора» проекта — он продолжает наблюдать за процессом разработки Python, принимая окончательные решения, когда это необходимо (не менее 50% рабочего времени по договоренности с DropBox).
Появившись сравнительно поздно, Python создавался под влиянием множества языков программирования. Так, например, влияние оказали такие языки, как:
- ABC: отступы для группировки операторов, высокоуровневые структуры данных;
- Modula-3: пакеты, модули;
- С, C++: некоторые синтаксические конструкции;
- Smalltalk: ООП;
- Java: обработка исключений и др.
Большая часть других особенностей Python (например, байт-компиляция исходного кода) также была реализована ранее в других языках.
Развитие языка происходит согласно четко регламентированному процессу создания, обсуждения, отбора и реализации документов PEP (Python Enhancement Proposal) — предложений по развитию Python.
В 2008 году, после длительного тестирования, вышла первая версия Python 3000 (или Python 3.0, также используется сокращение Py3k). В Python 3000 устранены многие недостатки архитектуры с максимально возможным (но не полным) сохранением совместимости со старыми версиями Python. На сегодня поддерживаются обе ветви развития (Python 3.x и 2.x) (сравнение и рекомендации), однако получать новый функционал будет только версия 3 6.
Ключевые вехи развития языка приведены в Таблице 2.1.1.
Таблица 2.1.1 — Основные вехи развития языка Python ¶
Актуальная версия (дата выхода) (могла устареть)