Библиотека для распространения
Управление ресурсами.
Мы должны решить, кто отвечает за совместно используемую информацию.
Возвращает ли csvgetline исходные данные или делает копию? Мы решили, что csvgetline возвращает указатель на исходные данные, которые будут перезаписаны при чтении следующей строки. Поля будут созданы в копии введенной строки, и csvfield будет возвращать указатель на поле в копии строки. При таких соглашениях пользователь должен сам создавать дополнительную копию, если какая-то конкретная строка или поле должны быть сохранены или изменены, и пользователь же отвечает за высвобождение этой памяти после того, как необходимость в ней отпадет.
Кто открывает и закрывает файл ввода? Кто бы ни открывал вводимый файл, он же должен его закрыть; парные действия должны выполняться на одном уровне или в одном и том же месте. Мы будем исходить из предположения, что csvgetline вызывается с указателем FILE, определяющим уже открытый файл; по окончании обработки файл будет закрыт вызывающей стороной.
Управление ресурсами, используемыми совместно или передающимися между библиотекой и вызывающими ее программами, – сложная задача; часто существуют веские, но противоречивые доводы в пользу различных решений. Ошибки и недопонимание при разделении ответственности за управление совместно используемыми ресурсами – характерный источник ошибок.
Обработка ошибок.
Когда csvgetline возвращает NULL, не существует способа отличить выход на конец файла от ошибки вроде нехватки памяти; точно так же и доступ к несуществующему полю не вызовет ошибки. По аналогии с terror мы могли бы добавить в интерфейс еще одну функцию, csvgeteгror, которая сообщала бы нам о последней ошибке, но для простоты мы не будем включать ее в данную версию.
Надо принять за постулат, что функции библиотеки не должны просто прерывать исполнение при возникновении ошибки; статус ошибки должен быть возвращен вызывающей стороне. Также не следует печатать сообщения или выводить окна диалога, поскольку функции библиотеки могут исполняться в системах, где такие сообщения будут мешать чему-то еще. Обработка ошибок – тема, достойная отдельного обсуждения, и мы еще вернемся к ней далее в этой главе.
Спецификация.
Все описанные выше решения и допущения должны быть собраны воедино в спецификацию, описывающую средства, предоставляемые csvgetline, и методы ее использования. В больших проектах спецификация должна предшествовать реализации, при этом, как правило, разные люди и даже разные организации создают спецификацию и пишут код. Однако на практике эти работы часто производят параллельно – тогда спецификация и код эволюционируют совместно; иногда же "спецификация" пишется уже после разработки программы, чтобы приблизительно описать, что же делает код.
Самый грамотный подход – писать спецификацию как можно раньше и (как делали мы) пересматривать ее по мере реализации кода. Чем тщательнее и вдумчивее будет написана спецификация, тем больше вероятность создать хороший продукт. Даже при создании программ для собственного пользования важно подготовить достаточно осмысленную спецификацию, поскольку она требует анализа существующих проблем и четкого фиксирования принятых решений.
В нашем случае спецификация будет включать в себя прототипы функций и детальное описание их поведения, сделанных допущений и распределения ответственности:
Поля разделены запятыми. Поле может быть заключено в двойные кавычки: "…". Поле, заключенное в кавычки, может содержать запятые,но не символы перевода строки. Поле, заключенное в кавычки, может содержать символы двойных кавычек, представляемые парой двойных кавычек Поле может быть пустым; "" и пустая строка равно представляют пустое поле. Предваряющие и заключительные пробелы сохраняются. char *csvgetline(FILE *f); читает одну строку из файла ввода f; подразумевается, что строки во вводе оканчиваются символами \г, \п, \г \п или ЕОЕ. возвращает указатель на строку (символы конца строки удаляются) или NULL, если достигнут EOF. строки могут иметь произвольную длину; возвращается NULL, если превышен резерв памяти, строки рассматриваются как память, доступная только для чтения; для сохранения или изменения содержимого вызывающая сторона должна сделать копию. char *csvfield(int n); поля нумеруются начиная с 0. возвращает n-е поле из последней строки, прочитанной csvgetline; возвращает NULL, если n отрицательно или лежит за последним полем, поля разделяются запятыми. поля могут быть заключены в двойные кавычки, эти кавычки убираются; внутри двойных кавычек запятая не являетсяразделителем, а пара символов "" заменяется на ". в полях, не ограниченных кавычками, кавычки рассматриваются как обычные символы, может быть произвольное количество полей любой длины; возвращает NULL, если превышается резерв памяти, поля рассматриваются как память, доступная только для чтения; для сохранения или изменения содержимого вызывающая сторона должна сделать копию, при вызове до csvgetline поведение не определено. int csvnfield(void); возвращает количество полей в последней строке, прочитанной csvgetIine. при вызове до csvgetline поведение не определено.