Учебная страница курса биоинформатики,
год поступления 2010
Самодокументация. Тесты. Модули.
Кроме того, что программа выполняет свои функции, у неё бывает ещё и несколько других положительных качеств, которые она может уметь проявлять, например:
устойчивость – способность вести себя корректно когда условиях вокруг плохие, например, пользователь дал плохие данные на вход, например, ввёл матерное слово вместо числа (как правило, это подразумевает в первую очередь умение отловить со стороны пользователя заведомую лажу, и сообщение ему о том, что он дурак)
масштабируемость – способность программы работать на объёмах данных, сильно больше, чем запланировано изначально
надёжность – способность работать правильно на ЛЮБЫХ правильных данных. Например, не подвисать, если пользователь слишком часто потыкал в кнопочку или что-нибудь ещё в этом роде. Синоним: отсутствие глюков.
поддерживаемость – способность того же (или, ещё лучше, другого) человека спустя какое-то время поменять программу в соответствии с изменившейся картиной мира
Теоретики программирования насочиняли и много других полезных черт характера программы, к которым стоит стремиться, нам же сейчас интересны последние два.
На самом деле, они очень сильно взаимосвязаны.
Первый и самый эффективный способ улучшать их оба одновременно – писать код программы чисто и ясно. Обычно для этого нужно:
- Всегда стараться делить код на маленькие кусочки и выносить их в функции. При этом (вполне достижимый в большинстве случаев) идеал состоит в том, чтобы тело каждой функции было не больше 10 строк, было очевидно по содержанию просто из кода функции, при этом функция бы решала ровно одну задачу, и её название эту задачу описывало.
Называть функции и переменные всегда однородным образом и давать им имена, внятно объясняющие их назначение. Принято для функций использовать глаголы в повелительном наклонении (или глагольные группы), а для переменных – существительные (или группы существительного). Также принято для имён переменных и функций использовать все маленькие буквы (а если имя состоит из нескольких слов, соединять их через подчёркивание), а для констант использовать все заглавные буквы.
- Сопровождать функции документацией (об этом ниже)
Полезная мелочь – использовать везде по ходу программы одинаковые с самим собой соглашения о том, где сколько ставить пробелов (например, вокруг скобок, операций, в начале строки), и вообще, использовать пробелы или табуляции для отступа.
Второй, весьма эффективный способ, состоит в том, чтобы для отдельных частей программы создавать автоматические тесты. Хорошо написанный набор автоматических тестов может заодно быть и хорошим описанием программы – и об этом тоже написано ниже.
Модули
Возвращаясь к первому способу добиваться и надёжности, и поддерживаемости программы – когда функций становится слишком много, или же, когда они очень явно распадаются на несколько групп по теме, которой они посвещены, становится полезно разложить разные функции в разные файлы.
Питонский файл, в котором лежит много функций (а может, заодно и описаний констант), называется модулем.
Мы уже умем их импортровать – import m делает вам доступным модуль m под именем m и функцию x из него под именем m.x. Ещё мы знаем, что from m import x делает доступной функцию x из модуля m сразу под именем x, при этом, из всего модуля нам становится доступной только она.
Чтобы создать свой модуль, который будет называться в питоне m, нужно создать файл m.py, и положить в него все определения функций, которые вы хотите в нём разместить.
Теперь находясь в той же директории вы можете сказать import m, и получить доступ к тому, что вы положили в этот файл. Конструкция import ищет модули сначала в текущей директории, а затем в нескольких директориях, в которые установлен сам питон.1
if __name__ == "__main__"
Так как модуль – это самый обычный питонский файл, то из этого следует, что мы можем любую питонскую программу – например, те, которые вы делали для домашних заданий – проимпортиоровать. Маленькая техническая подробность состоит в том, что для того, чтобы проимпортировать модуль, интерпретатор питона просто исполняет всё его содержимое. Поэтому если вы импортируете программу, которая рисует змейку, то конструкция import не завершится до тех пор, пока вы не закроете окошко.
В подобных случаях, если хочется сделать файл пригодным и для запуска как самостоятельную программу, и для использования в качестве модуля, в питоне есть способ различать, каким из этих двух способов запущен файл:
Если вы напишете такую программу и положите в файл m.py, то если вы его запустите, у вас на экране появится сообщение "I am standalone program", а если вы его проимпортируете, получится:
Самодокументация
Когда программа становится сложной, в ней становится осмысленно оставлять комментарии о том, чего хотел этой строкой сделать автор. В питоне есть несколько способов это делать.
Во-первых, есть комментарии. Комментарии в питоне начинаются с # и длятся до конца строки. Во всех остальных смыслах питон такие куски строк игнорирует:
– Это пример того, как НЕ надо поступать. В данном случае комментарий ничего в программе не поясняет. Комментарии к программам делят на несколько типов:
- те, которые описывают цель фрагмента кода,
- те, которые описывают, зачем этот фрагмент кода нужен,
- и те, которые описывают, как фрагмент кода работает.
Лучше всего комментарии первого рода. Если происходит что-то очень сложное, то иногда бывают допустимы комментарии второго рода. А если фрагмент кода в программе требует комментария третьего рода, то это значит, что у вас происходит что-то слишком сложное. В этом случае нужно программу разделить на более мелкие части, каждую из таких частей положить в отдельную функцию, написать к этой функции комментарий первого рода, и ещё придумать для функции название, которое хорошо описывает, что она делает.
Поэтому в питоне не рекомендуется писать коментарии через #, а вместо этого есть другой способ:
Первой строкой в функции можно написать в тройных кавычках описание этой функции. Этот способ называется самодокументацией, а почему – мы поймём на следующих занятиях.
1 site_begin = 10
2 site_end = 15
3
4 def site_len(seq):
5 """Return length of recognition site of sequence seq.
6
7 Recognition site is alignment columns defined by global variables
8 site_begin and site_end.
9
10 Return number of non-gap symbols within the recognition site.
11
12 Example:
13 >>> site_len("LAGFLDADGYFQ--VR--I---INRERK")
14 4
15 """
16 return len(seq[site_begin:site_end].replace("-", ""))
В жизни ситуация, когда описание функции ощутимо длиннее её тела, вполне допустимы и в сложных случаях безусловно приветствуются.
В таких описаниях функции принято:
- Описывать назначение функции в виде фразы в повелительном наклонении
- Описывать аргументы (если они не самоочевидны)
- Описывать возвращаемое значение
- Приводить примеры использования функции (притом лучше копировать такие примеры из диалога с питоном)
Совершенно аналогичным образом, если текст модуля начинается с тройных кавычек, то это есть документация к модулю.
Вы можете читать документацию к своим модулям наравне с документацией к встроенным питонским модулям с помощью функции help. Например, если у вас в текущей директории есть модуль m (файл m.py), то вы можете сказать так:
Тесты
Когда в опсиании функции мы привели пример использования этой функции, хотелось бы иногда проверять, что этот пример действительно всё ещё имеет отношение к жизни. Кроме того, так как в питоне никаким другим образом не контролируется соответствие программы её назначению, то это нужно делать с помощью тестов: писать примеры вызова функции и чего мы ожидаем от неё в качестве результата.
На оба эти задачи в питоне отвечает модуль doctest: он проходит по самодокументации всех функций в файле, ищет в ней примеры, исполняет их и смотрит, согласуются ли результаты с теми, что приведены в примере, и если нет, сообщает об этом.
В примере выше, если мы добавим в начало программы строчку
1 import doctest
а в конец программы (после определения функции site_len) строчку
1 doctest.testmod()
То питон нам выдаст:
********************************************************************** File "t.py", line 12, in __main__.site_len Failed example: site_len("LAGFLDADGYFQ--VR--I---INRERK") Expected: 4 Got: 3 ********************************************************************** 1 items had failures: 1 of 1 in __main__.site_len ***Test Failed*** 1 failures.
И действительно, на выбранном диапазоне букв-то 3, а не 4.
В случае, если тесты прошли успешно, питон не будет печатать ничего.
Типичная традиция состоит в том, чтобы делать питонские модули, которые не являются самостоятельными программами, самотестирующими. Делается это так:
Любознательные могут увидеть список этих директорий в переменной sys.path, и почитать исходные коды большинства модулей питона. Правда, некоторые из них написаны не на питоне, а на языке Си, и их тексты читать сложнее. (1)