15-й час

Лаборатория доктора Франкенштейна (продолжение)

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

Создание программных компонентов многократного использования (продолжение)

  В конце предыдущей главы мы получили работающую версию модуля fixhash.py, который можно запустить в работу из любого каталога с указанием в командной строке того каталога, который нужно протестировать. Более того, программа автоматически проанализирует все каталоги, вложенные в текущий.

  Впрочем, будущий пользователь может как раз захотеть, чтобы программа не выполняла рекурсивный поиск файлов. (Рекурсивным называется такой поиск, который начинается с заданного каталога с последующим посещением всех вложенных подкаталогов, принадлежащих текущему дереву каталогов. Существуют и другие понятия рекурсии, но в данном случае мы имеем в виду только способ поиска файлов). Чтобы предоставить пользователю возможность выбирать схему поиска, добавим ещё один параметр командной строки, который будет включать или выключать рекурсивный поиск. В листинге 15.1 показан новый вариант ядра программы.

if __name__ == "__main__" :

  nmod=0

  recurse=0

  if len(sys.argv)>1:

    if sys.argv[l]=='-r':

      recurse=1

      if recurse and len(sys.argv)>2:

        dirnames=sys.argv[2:]

     elif recurse and len(sys.argv)<2:

       dirnames=["."]

     else:

       dirnames=sys.argv[1:]

     else:

       dirnames=["."]

  for s in dirnames:

    if not recurse:

      dlist=os.listdir(s)

      fixdirectory(0,s,dlist)

    else:

      os.path.walk(s,fixdirectory,0)

      nmod=nmod+getnfiles()

 

 sys.stderr.write ( "Files modified: " + 'nmod' + "\n" )

  На рис. 15.1 показан результат выполнения вызова - модуля без аргументов (по умолчанию): python fixhash.py, а на рис. 15.2 представлен результат выполнения того же модуля с установкой аргумента рекурсии и точкой после него для указания анализа текущего каталога: python fixhash.py -r ...

В листинге 15.2 показан окончательный вариант модуля fixhash.py.

Рис. 15.1. Вызов модуля по умолчанию: fixhash.py

Рис. 15.2. Вызов модуля с аргументом рекурсии: fixhash.py -r

Листинг 15.2. Окончательный вариант модуля fixhash.py

#!с:\python\python.exe

 

import os

import sys

import fileinput

from stat import

 

pbang="#!"+ sys.executable

nfiles = 0

nm=""

 

def initial(v=0):

  global nm

  if v!=0:

    if sys.platform=="Win32":

      sys.stderr.write("Python Version "+\

        sys.version+" Running on Windows\n")

    elif sys.platform=="linux2":

      sys.stderr.write("Python Version "\

        +sys.version+" Running on Linux\n")

    else:

      stderr.write("Python Version "+sys.version+" \

        Running on "+sys.platform+"\n")

      nm=os.path.split(sys.argv[0])[1]

 

def getnfiles():

  return nfiles

 

def rewritefile(fip):

  global pbang

  mode = os.stat(fip) [ST_MODE]

  for line in fileinput.input(fip, inplace=1):

    if fileinput.isfirstline():

      if line [:2]=="#!":

        sys.stdout.write(pbang+"\n")

      else:

        sys.stdout.write(pbang+"\n")

        sys.stdout.write(line)

    else:

      sys.stdout.write(line)

      fileinput.close()

      os.chmod(fip,mode)

 

def fixdirectory(arg, d, dlist):

  global nfiles

  nfiles = 0

  for i in dlist:

    if i==nm:

      continue

    if len (i)>4:

      fname=os.path.join (d, i)

      if fname[-3:]==".py":

        if arg!=0:

          sys.stderr.write("Checking"+fname+\

            "...\n" )

          for line in fileinput.input(fname):

            if fileinput.isfirstline():

              if line[:-1]!=pbang:

                fileinput.close()

              t=os.access(fname, os.W_OK)

             if t==0:

              if arg!=0:

                sys.stderr.write(fname+" is not\

                  writable; skipping.\n"

              else:

                if arg!=0:

                  sys.stderr.write("Modifying " +\

                    fname + "...\n")

                  rewritefile(fname)

                  nfiles=nfiles+1

                  break

             else:

               fileinput.close()

               break

               return nfiles

 

def fixhash(d, r, v):

  nmod=0

  if type(d)!=type([]):

    dl=list(d)

  else:

    dl=d 

    initial(v)

    for i in dl:

      if not r:

        dlist=os.listdir(i)

        fixdirectory(v, i, dlist)

      else:

        os.path.walk(i, fixdirectory, v)

        nmod=nmod+getnfiles()

 

  return nmod

 

if __name__=="__main__":

  nmod=0

  recurse=0

  if len(sys.argv)>1:

    if sys.argv[l]=='-r':

      recurse=recurse+1

      if recurse and len(sys.argv)>2:

        dirnames=sys.argv[2:]

      elif recurse and len(sys.argv)<2:

        dirnames=["."]

    else:

      dirnames=sys.argv[1:]

  else:

    dirnames=["."]

    nmod=fixhash(dirnames, recurse, 1)

 

  sys.stderr.write ( "Files modified: " + 'nmod' + \

    "\n" )

  Обратите внимание, что заключение выполняемой части программы в конструкцию if __name__=="__main__": даёт возможность пользователю одинаково успешно выполнять файл fixhash.ру как уже готовую программу либо импортировать этот модуль в свою программу. В листинге 15.3 показано, как выполняется импортирование.

Листинг 15.3. Программа testhash.py

#!с:\python\python.exe

 

import sys

import fixhash

 

if __name__=="__main__":

  nmod=0

  recurse=0

  verbose=0

  if len(sys.argv)>1:

    if sys.argv[l]=='-r':

      recurse=recurse+1

      if recurse and len(sys.argv)>2:

        dirnames=sys.argv[2:]

      elif recurse and len(sys.argv)<2:

        dirnames=["."]

      else:

        dirnames=sys.argv[1:]

   else:

     dirnames=["."]

     nmod=fixhash.fixhash(dirnames, recurse, \

       verbose)

 

 if verbose:

   sys.stderr.write("Files modified: "+'nmod'\

     +"\n")

 else:

   sys.exit(nmod)

Документирование пользовательских модулей

  Одной из важных задач, которую Вам предстоит ещё выполнить для усовершенствования самого модуля и повышения возможности его эффективного использования пользователями, является документирование того, что и как он может делать. Это минимальные требования к документации модуля. Я предпочитаю включать ещё описания возможных ошибок, а также прилагать документацию в формате HTML. Действительно, нет проблем скопировать строки документации в Ваш редактор HTML и сохранить их отдельным файлом, доступным для просмотра в Internet.

  Вы убедитесь в том, что модули, снабжённые подробной документацией, пользуются значительно большим спросом и популярностью, чем те, с особенностями которых программистам приходится разбираться самостоятельно. Не так давно я собрал воедино разработанные мной сценарии оболочки, которые, на мой взгляд, казались весьма полезными и вполне понятными, поэтому я не счел нужным подготовить соответствующую документацию на них. Я объявил по своему списку рассылки о том, где любой желающий может получить доступ к моим утилитам. Уже на следующий день я получил три письма, сообщавших, что мои разработки, вполне вероятно, весьма интересны и полезны, но в них совершенно нельзя разобраться непосвященному. Я не стал ожидать следующих сообщений с критикой и быстро составил необходимую документацию. Сделал я это действительно быстро. До этого гораздо больше времени я потратил на размышления, стоит документировать мои модули или не стоит.

  Вместо того чтобы вновь приводить здесь весь код нашей программы fixhash.py с добавленными строками документации, рассмотрим лучше в качестве примера документацию лишь одной функции fixhash (листинг 15.4). Подобные строки с описанием возможностей и особенностей применения можно добавить во все функции, а также в первые строки самого модуля. Займитесь этим самостоятельно в качестве домашнего задания.

Листинг 15.4. Документирование функции fixhash

def fixhash ( d, r, v ):

  """

Данная функция вызывает функцию fixdirectory ( ) для каждого каталога, указанного в списке d. Если r не равно нулю, функция os.path.walk() выполняется в рекурсивном режиме поиска файлов в дереве каталогов, начиная с корневого каталога. Если v не равно нулю, то выводятся сообщения об ошибках. Возвращает .количество измененных файлов.

  """

  nmod=0

  if type(d)!=type([]):

    dl=list(d)

  else:

    dl=d

    initial(v)

    for i in dl:

      if not r:

        dlist=os.listdir(i)

        fixdirectory(v, i, dlist)

      else:

        os.path.walk(i, fixdirectory, v)

        nmod=nmod+getnfiles()

  return nmod

  На рис. 15.3 показан пример документирования функций в формате HTML. С полной документацией на английском языке всех модулей и функций, использованных в данной книге, можно познакомитесь по адресу http://www.pauahtun.org/TYPython/

Рис. 15.3. Пример документации функций модуля fixhash.py в формате HTML

  Следует отличать в Python строки документации от строк комментариев в программе. Оба средства весьма эффективны, но служат для разных целей, а потому к ним предъявляются разные требования. Помню, когда я только осваивал премудрости программирования, то часто обращался за помощью к более опытному инженеру, с которым работал в одном отделе. Сначала я сопровождал свой программный код очень подробными комментариями, считая, что тем самым делаю его максимально понятным. Мой товарищ долго рассматривал программу, задумчиво теребя пальцами бороду, потом сказал, что в ней трудно разобраться, поскольку тут так много комментариев, что в них теряется код программы. Я стал спорить, что комментарии для того и придумали, чтобы на "человеческом" языке разъяснить, что делает программа в том или ином месте. Тогда он просто переписал мою программу, безжалостно сокращая комментарии до минимума. Ещё до того как он закончил трудиться над моей программой, я уже смог обнаружить свои ошибки в коде.

  Если комментарии должны быть ёмкими, то в документации Вы можете позволить себе подробные описания, естественно, в разумных пределах. Документация размещается в первых строках программы, поэтому не мешает проследить логику выполнения программного блока. Кроме того, строки документации можно вывести на экран для просмотра отдельно от программного кода.

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

i=i+1 # Увеличить значение i на единицу

  Если весь Ваш код будет пестреть подобными комментариями, то это приведёт в бешенство кого угодно.

  Лучше всего будет дать описание функции в документации, а затем снабдить краткими комментариями ключевые моменты её выполнения, как показано на примере в листинге 15.4.

Листинг 15.4. Пример использования документации и комментариев

def FindYear(s):

 """Поиск строки по значению года, заданному в формате

"(1999)" с пробелом впереди. Возвращает строку без значения года, за которой следует год в числовом формате. Если год не указан, возвращается None.

 " " "

  global _yearre

  global _yearrec

  if _yearrec==0:

    _yearrec=re.compile(_yearre)

    so=_yearre.search(s)

    if so==None:

      return None

    st, ep =so.span()

    st=st+" Удаление пробела"

    у=string.atoi(s[st+1:ep-1])

    rt=s[:st-1] + s[ep:]

    return rt, у

  Комментарий всего лишь привлекает Ваше внимание к строке программного кода, смысл которой Вам может показаться недостаточно очевидным: "С помощью строки 15 мы просто удаляем нежелательный пробел".

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

Тестирование программных компонентов

  Наверное, Вы уже убедились, насколько важно бывает для правильного выполнения программы определить текущее значение или состояние переменной, флага или модуля. Наш модуль fixhash имеет программный блок для самотестирования, следующий за инструкцией:

if __name__ == "__main__":

(см. строку 89 в листинге 15.2). Как мы помните, данный блок инструкции if выполняется только в случае запуска модуля как самостоятельной программы, но не будет выполняться при импортировании этого модуля в другую программу. Без блока тестирования часто оказывается просто невозможно разобраться с тем, как работает программа и почему в импортированном модуле происходит сбой. Иногда строки тестирования вводят временно, только на время отладки, иногда их оставляют в коде программы навсегда, если это будет полезным с точки зрения будущих пользователей.

  Ниже приведён список ситуаций, когда выполнение тестирования будет полезным для отладки или выполнения программы.

Поиск модулей независимых изготовителей для своих программ

  На примере модуля fixhash я пытался максимально подробно раскрыть процесс разработки программного блока многоразового использования. Но прежде чем приступать к разработке своего модуля, нужно ответить на следующие два вопроса. Во-первых, разрабатывал ли кто-либо до Вас подобные модули? Во-вторых, существуют ли готовые модули, которые могут быть использованы в качестве компонентов Вашего модуля? Как обычно, наиболее подходящим местом, где можно найти информацию такого poдa, является домашняя страница Python (http://wvw.python.org/). На этой Web-странице Вы можете воспользоваться средством поиска по ключевым словам. Таким способом можно провести поиск по серверу Python, серверам телеконференций или по всей сети Internet. В телеконференциях представлено много сообщений о новых программных средствах, приёмах, идиомах и справочных пособиях. Самое важное для Вас — знать, что, где и как искать. Тема поиска информации и программных продуктов в Internet выходит за рамки этой книги. Много полезной и справочной информации Вы найдёте на странице, размещенной по адресу http:://www.python.org/search/. Если Вы не сильны в средствах поиска, начните свою охоту на модули с этой страницы, которая популярно объяснит Вам, как правильно расставить свои сети в Internet.

  Если Вы зарегистрируетесь в списках рассылки пользователей Python, то сможете направлять поисковые запросы или получать свежайшие извещения о новых программных продуктах, среди которых могут оказаться полезные Вам. Для регистрации в списке рассылки обращайтесь по адресу http:://www.python.org/psa/MailingLists.html.

  *Прим. В. Шипкова: готовьтесь к тому, что рассылка эта хоть и весьма полезна, но тем не менее - на английском языке!

Резюме

  В этой и предыдущей главах мы рассматривали в деталях процесс разработки модулей многократного использования. Был создан модуль fixhash, который мы усовершенствовали, протестировали и снабдили документацией. Вы узнали, что прежде чем приступать к разработке своих модулей, следует узнать, не сделал ли этого уже кто-нибудь до Вас. Вполне возможно также, что Вы найдёте готовые программные блоки, которые можно использовать в качестве компонентов Вашего модуля. Хотя мы и не рассмотрели подробно процесс поиска данных и программ в Internet, Вы узнали, где можно почерпнуть такую информацию.

  Следующая глава завершает часть II. Мы создадим новый полнофункциональный класс и тщательно протестируем его работу. Впрочем, нашей конечной целью будет не создание класса, а усвоение на практике принципов объектно-ориентированного программирования. Вы увидите, что в следующей главе буду говорить не только я — программные коды будут говорить сами за себя.

Практикум

Вопросы и ответы

  Насколько важно использование внешних модулей в современном программировании?

  Без использования модулей Вы будете вынуждены каждый раз изобретать велосипед. В том, насколько важна и удобна возможность многократного использования модулей в программах, Вы очень быстро разберётесь, как только испытаете все преимущества на практике.

Контрольные вопросы

  1. Как долго следует выполнять тестирование программы?

    а) 25 раз.

    б) 50 раз.

    в) Пока она не будет успешно выполнена до конца хотя бы один раз.

    г) Пока Вы не добьетесь сбоя программы, а затем заставите её успешно выполниться до конца.

  2. Если Вы. нашли два модуля разных изготовителей, по какому принципу Вы выберете модуль для использования в своей программе?

    а) Продукт той компании, которой Вы больше доверяете.

    б) Тот модуль, который обладает большим быстродействием.

    в) Тот модуль, который лучше задокументирован.

    г) Тот модуль, который Вам рекомендовали друзья и знакомые.

Ответы

  1. г. Программу следует трестировать до тех пор, пока Вам не удастся добиться сбоя в её выполнении. Не успокаивайтесь на том, что Вам удалось однажды успешно выполнить программу. Постарайтесь создать самые невероятные условия запуска программы до того, как этим займутся ваши пользователи.
  2. в. Безусловно, все перечисленные факторы следует учитывать, но я бы всё же отдал предпочтение хорошо документированному модулю. Если модуль потенциально сильнее, но плохо описан, то Вы просто не сможете реализовать все его возможности, тем более, если у Вас нет опыта разбираться в хитросплетениях кода, написанного другим программистом.

Примеры и задания

  Добавьте ещё одну опцию командной строки в модуль testhash.py, которая даст возможность пользователю включать или отключать показ сообщений об ошибках, устанавливая значение аргумента verbose. Рекомендую Вам поближе познакомиться с модулем getargs, который можно найти и загрузить по адресу http://www.pauahtun.org/ftp.html. Естественно, он хорошо задокументирован (http://www.pauahtun.org/getargs.html).

  Если Вы захотите создать свою библиотеку модулей, Вам потребуется какой-нибудь архиватор файлов. Лучше всего подойдёт WinZip. Это приложение можно загрузить из Internet по адресу http://www.winzip.com/. Я предпочитаю использовать архиватор WinZip вместо tar или gzip, даже если предполагается использование архива в UNIX. Утилита gzip может распаковывать архивные файлы WinZip, а эти файлы меньше по размерам, чем родные файлы gzip.

  *Прим. В. Шипкова: я бы рекомендовал к использованию WinRAR Александра Рошала.

  Если Вы больше хотите узнать о методах и приёмах тестирования программ, обратитесь к книге Brian Marrick, The craft of software testing, Prentice Hall, 1999 (Брайан Маррик, "Искусство тестирoвания программ"). Для Брайана характерен оригинальный подход к проблеме и живой стиль изложения. В такой же манере написана его статья Classic Testing Mistakes ("Классические ошибки при тестировании"), которую Вы можете найти по адресу http://www.rstcorp.com/marick/classic/mistakes.html. Внимательно просмотрите эту страницу. Здесь Вы найдёте ссылки на другие публикации по тестированию программ, например по адресу http://www.rstcorp.com/marick.