Иллюстрированный самоучитель по практике программирования

Библиотека для распространения

Теперь с учетом того, чему мы научились на опыте прототипа, попробуем создать библиотеку общего назначения. Наиболее очевидные требования такие: csvgetline должна быть более устойчива, то есть уметь обрабатывать и длинные строки, и большое количество полей; более осторожно надо подойти и к синтаксическому разбору полей.

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

Интерфейс.
Мы выработали решение о трех базовых операциях:

char *csvgetline(FILE *): читает новую CSV строку;char *csvf ield(int n):
возвращает n-е поле текущей строки;
int csvnf leld(void): возвращает число полей в текущей строке.

Какое значение должна возвращать csvgetline? Желательно, чтобы она возвращала побольше полезной информации; тогда напрашивается возвращение того же количества полей, как и в прототипе. Но тогда количество полей будет подсчитываться даже в случае, если поля эти больше использоваться не будут. Еще один вариант возврата – длина вводимой строки, но это значение зависит от того, включать ли в длину завершающий символ перевода строки. После ряда экспериментов мы пришли к выводу, что csvgetline должна возвращать указатель на оригинальную строку ввода или NULL, если был достигнут конец файла.

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

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

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

Поля нумеруются с нуля. Как быть, если пользователь запросит несуществующее поле, вызвав csvfield(-1) или csvfield(100000)? Мы могли бы возвращать " " (пустую строку), поскольку это значение можно выводить или сравнивать; программам, которые работают с различным количеством полей, не пришлось бы принимать специальных предосторожностей на случай обращения к несуществующему полю. Однако этот способ не предоставляет возможности отличить пустое поле от несуществующего. Второй вариант – выводить сообщение об ошибке или даже прерывать работу; несколько позже мы объясним, почему так делать нежелательно. Мы решили возвращать NULL – общепринятое в С значение для несуществующей строки.

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

Только функция csvgetline занимается управлением памятью; вне ее ничего о методах организации памяти не известно. Лучше всего осуществлять такую изоляцию через интерфейс функции: получается (то есть видно снаружи), что csvgetline читает следующую строку – вне зависимости от ее размера, csvfield(n) возвращает указатель на байты n-го поля текущей строки, a csvnfields возвращает количество полей в текущей строке.

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

Если пользователь вызывает только csvgetline, то нет надобности разделять строку на поля; это можно сделать по специальному требованию. Происходит ли разделение полей ретиво (eager, непосредственно при чтении строки), лениво (lazy, только когда нужно посчитать количество полей) или очень ленива (very lazy, выделяется только запрошенное поле) – еще одна деталь реализации, скрытая от пользователя.

Если Вы заметили ошибку, выделите, пожалуйста, необходимый текст и нажмите CTRL + Enter, чтобы сообщить об этом редактору.