Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
kernigan_paik.doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
2.91 Mб
Скачать

4.2. Прототип библиотеки

Вряд ли нам удастся получить удачный проект библиотеки интерфей­са с первой попытки. Как написал однажды Фред Брукс (Fred Brooks), "заранее планируйте выкинуть первую версию — все равно придется". Брукс писал о больших системах, но суть остается той же и для любой нормальной программы. Как правило, до тех пор пока вы не создали пер­вой версии и не поработали с ней, трудно представить себе все аспекты ИЬаботы программы настолько хорошо, чтобы спроектировать достойный продукт.

Исходя из этих соображений, мы начнем создавать библиотеку CSV с версии "на выброс", с прототипа. В первой версии мы проигнорируем многие проблемы, которые должны быть решены в грамотной библиотеке, однако она будет достаточно полной, чтобы ее можно было использовать, и с помощью этой версии мы поближе познакомимся с задачей.

Начнем с функции csvgetline, которая считывает одну строку данных CSV из файла в буфер, разделяет ее на поля массива, удаляет кавычки и возвращает количество полей. В течение многих лет мы уже не раз писали что-то подобное на различных языках, так что задание нам знакомо. Вот версия-прототип на С; мы пометили код вопросами, потому что это всего-навсего прототип:

? char buf[200]; /* буфер вводимой строки */

? char *field[20]; /* поля */

?

? /* csvgetline: читает и разбирает строку, */

? /* возвращает количество полей */

? /* пример ввода: "LU",86.25,"11/4/1998","2:19РМ",+4.0625 */

? int csvgetline(FILE *fin)

? {

? int nfiled;

? char *p, *q;

?

? if (fgets(buf, sizeof(buf), fin) == NULL)

? return –1;

? nfield = 0;

? for (q = buf; (p=strtok(q, ",\n\r")) != NULL; q = NULL)

? field[nfield++] = unquote(p);

? return nfield;

? }

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

Формат CSV слишком сложен, чтобы разбирать его с помощью scanf, поэтому мы использовали функцию strtok из стандартной библиотеки С. Каждый вызов strtok(p, s) возвращает указатель на первую лексему (token) из строки р, состоящую из символов, не входящих в s; strtok обрывает эту лексему, заменяя следующий символ исходной строки нуле­вым байтом. При первом вызове strtok первым аргументом является сканируемая строка; при следующих вызовах для обозначения того, что сканирование должно продолжиться с той точки, где закончился преды­дущий вызов, в этом месте стоит NULL. Интерфейс получился убогим. Поскольку между вызовами st rtok хранит переменную в некоем неизве­стном месте, в каждый момент может исполняться только одна последо­вательность вызовов; несвязанные перемежающиеся вызовы будут кон­курировать и мешать друг другу.

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

? /* unquote: удаляет открывающие и закрывающие кавычки */

? char *unquote(char *p)

? {

? if (p[0] ==’"/){

? if (p[strlen(p)-1] == '"’)

? p[strlen(p)-1] = ‘\0';

? p++;

? }

? return p;

? }

Простая тестовая программа поможет удостовериться, что csvgetline работает:

? /* csvtest main: тестирует функцию csvgetline */

? int main(void)

? {

? int i, nf;

?

? while ((nf = csvgetline(stdin)) != -1)

? for (i = 0; i < nf; i++)

? printf("n<wie[%d] = '%s'\n, i, field[i]);

? return 0;

? }

При выводе printf заключает поля в парные простые кавычки; это зри­тельно разделяет поля и помогает выявить ошибки некорректной обра­ботки пробелов.

Мы можем прогнать через этот тест результаты работы getquotes. tcl:

% getquotes.tcl | csvtest

….

поле[0] = 'LU’

поле[1] = '86.375'

поле[2] = '11/5/1998'

поле[3] = '1:01PM'

поле[4] = '-0.125'

поле[5] = '86'

поле[6] = '86.375'

поле[7] = '85.0625'

поле[8] = '2888600'

поле[0] = 'Т'

поле[1] = '61.0625'

(Заголовки HTTP мы убрали.)

Итак, у нас есть прототип, который, кажется, в состоянии работать В с данными вроде приведенных выше. Однако теперь было бы логично опробовать его на чем-то еще (особенно если мы планируем распространять эту библиотеку). Мы нашли еще один Web-сайт, позволяющий скачать биржевые котировки и получить файл с той же, собственно, информацией, но представленной в несколько иной форме: для разделения записей вместо символа перевода строки используется символ возврата каретки (\г), а в конце файла завершающего возврата каретки нет. Выглядят новые данные так (мы отформатировали их, чтобы они умеща-Ниись на странице):

"Ticker","Price","Change","Open","Prev Close","Day High",

"Day Low","52 Week High","52 Week Low","Dividend",

"Yield","Volume","Average Volume","P/E"

"LU",86.313,-0.188,86.000,86.500,86.438,85.063,108.50,

36.18,0.16,0.1,2946700,9675000,N/A

"T",61.125,0.938,60.375, 60.188, 61.125, 60.000, 68. 50,

46.50,1.32,2.1,3061000, 4777000,17.0

"MSFT",107.000,1.500,105.313,105.500,107.188,105. 250,

119.62,59.00,N/A,N/A,7977300,16965000, 51.0

При таком вводе наш прототип позорно провалился.

Мы спроектировали наш прототип, изучив только один источник данных, тестирование провели на данных из того же источника. Ста­ло быть, нечего удивляться тому, что первое же столкновение с дан­ными из другого источника привело к гибельным последствиям. Длинные строки на вводе, большое количество полей, непредусмот­ренные или пропущенные разделители — все это вызывает проблемы. Наш ненадежный прототип подходит только для индивидуального ис­пользования или в целях демонстрации принципиальной пригодности выбранного подхода, но не более того. Что ж, пришло время перерабо­тать проект.

При создании прототипа мы сделали ряд предположений — явных и неявных. Ниже перечислены некоторые из наших решений, зачастую не самых подходящих для универсальной библиотеки. Каждое поднимает вопрос, требующий более тщательной проработки.

• Прототип не способен обработать длинные строки или большое ко­личество полей. Он может выдавать неправильные ответы или во­обще зависать, потому что в нем нет даже проверки на переполне­ние, не говоря уже о возвращении каких-то разумных значений при возникновении ошибок.

• Предполагается, что ввод состоит из строк, оканчивающихся симво­лом перевода строки.

• Поля разделены запятыми; если поле заключено в кавычки, послед­ние удаляются. Не предусмотрен случай вложенных кавычек или запятых-

• Вводимая строка не сохраняется; в процессе генерации полей она переписывается.

• При переходе от одной строки к следующей никакие данные не со­храняются; если что-то надо запоминать, то следует создавать ко­пию.

• Доступ к полям осуществляется через глобальную переменную — массив field, который используется совместно функцией csvget-line и функцией, которая ее вызвала; нет никакого контроля досту­па к содержимому полей или указателям. Не предусмотрено ника­ких средств для предотвращения доступа за последнее поле.

• Использование глобальных переменных делает проект непригод­ным для многонитевой среды или даже для одновременного испол­нения двух параллельных вызовов.

• Функция csvgetline читает только из уже открытых файлов; откры­тие лежит целиком на совести вызывающего кода.

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

• Возвращаемое значение есть количество полей в строке; для подсче­та этого значения строка должна быть разделена на поля. Не пре­дусмотрено никакого механизма для определения ошибок конца файла.

• Нет никакой возможности изменить ни одно из перечисленных свойств, не внося изменений в код.

В этом длинном, но далеко не полном списке приведены те решения, которые мы приняли на этапе проектирования, — и каждое решение навсегда вплетено в код. Это приемлемо для временной версии, разби­рающей файлы известного формата, поступающие из одного конкрет-його источника. Но что будет, если формат изменится, или запятая по­явится внутри кавычек, или сервер выдаст длинную строку или много полей?

Может показаться, что со всем этим нетрудно справиться, ведь "биб­лиотека" мала и, в конце концов, является всего лишь прототипом. Иредставьте, однако, что этот код, пролежав в забвении месяцы или годы, в какой-то момент станет частью большой программы, специфика­ции которой будут меняться. Как адаптируется csvgetline? Если про­грамму будут использовать другие люди, то скороспелые решения в ее проектировании могут вызвать проблемы, которые проявятся через дол­гое время. К сожалению, история многих интерфейсов подтверждает это заявление: большое количество временного, чернового кода просачива­ется в большие программные системы, в которых этот код так и остается ”грязным” и зачастую слишком медленным.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]