ЗНАЕТЕ ЛИ ВЫ?

Обработка исключений ввода-вывода



 
 

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

import System.Environment import System.IO

 

main = do

(fileName:_) <– getArgs contents <– readFile fileName

 
 

putStrLn $ "В этом файле " ++ show (length (lines contents)) ++ " строк!"

Очень простая программа. Мы выполняем действие ввода- вы- вода getArgs и связываем первую строку в возвращённом списке с идентификатором fileName. Затем связываем имя contents с содер- жимым файла. Применяем функцию lines к contents, чтобы полу- чить список строк, считаем их количество и передаём его функции show, чтобы получить строковое представление числа. Это работа- ет – но что получится, если передать программе имя несуществую- щего файла?

$ ./linecount dont_exist.txt

 
 

linecount: dont_exist.txt: openFile: does not exist (No such file or directory)

Ага, получили ошибку от GHC с сообщением, что файла не сущес- твует! Наша программа «упала». Но лучше бы она печатала красивое сообщение, если файл не найден. Как этого добиться? Можно про-


 

 
 

верять существование файла, прежде чем попытаться его открыть, используя функцию doesFileExist из модуля System.Directory.

import System.Environment import System.IO

import System.Directory

 

main = do

(fileName:_) <– getArgs

fileExists <– doesFileExist fileName if fileExists

then do

contents <– readFile fileName putStrLn $ "В этом файле " ++

show (length (lines contents)) ++ " строк!"

 
 

else putStrLn "Файл не существует!"

Мы делаем вызов fileExists <– doesFileExist fileName, потому что функция doesFileExist имеет тип doesFileExist :: FilePath –> IO Bool; это означает, что она возвращает действие ввода-вывода, со- держащее булевское значение, которое говорит нам, существует ли файл. Мы не можем напрямую использовать функцию doesFileExist в условном выражении.

 
 

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

import Prelude hiding (catch) import Control.Exception import System.Environment

 

countLines :: String -> IO () countLines fileName = do

contents <- readFile fileName

putStrLn $ "В этом файле " ++ show (length (lines contents)) ++ " строк!"

 

handler :: IOException -> IO ()


 

handler e = putStrLn "У нас проблемы!"

 

main = do

(fileName:_) <- getArgs

 
 

countLines fileName `catch` handler

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

 
 

Попробуем:

$ ./linecount linecount.hs В этом файле 17 строк!

 
 

$ ./linecount dont_exist.txt У нас проблемы!

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

 
 

Простой перехват всех типов исключений в одном обработчи- ке – плохая практика в языке Haskell, так же как и в большинстве других языков. Что если произошло какое-либо другое исключение, которое мы не хотели бы перехватывать, например прерывание программы? Вот почему мы будем делать то же, что делается в дру- гих языках: проверять, какой вид исключения произошел. Если это тот вид, который мы ожидали перехватить, вызовем обработчик. Если это нечто другое, мы не мешаем исключению распространять- ся далее. Давайте изменим нашу программу так, чтобы она перехва- тывала только исключение, вызываемое отсутствием файла:

import Prelude hiding (catch) import Control.Exception import System.Environment

import System.IO.Error (isDoesNotExistError)

 

countLines :: String -> IO () countLines fileName = do

contents <- readFile fileName

putStrLn $ "В этом файле " ++ show (length (lines contents)) ++


 

" строк!"

 

handler :: IOException -> IO () handler e

| isDoesNotExistError e = putStrLn "Файл не существует!"

| otherwise = ioError e

 

main = do

(fileName:_) <- getArgs

 
 

countLines fileName `catch` handler

Программа осталась той же самой, но поменялся обработчик, ко- торый мы изменили таким образом, что он реагирует только на одну группу исключений ввода-вывода. С этой целью мы воспользовались предикатом isDoesNotExistError из модуля System.IO.Error. Мы при- меняем его к исключению, переданному в обработчик, чтобы опре- делить, было ли исключение вызвано отсутствием файла. В данном случае мы используем охранные выражения, но могли бы использо- вать и условное выражение if–then–else. Если исключение вызвано другими причинами, перевызываем исключение с помощью функ- ции ioError.

ПРИМЕЧАНИЕ.Функции try, catch, ioError и некоторые другие объявлены одновременно в модулях System.IO.Error (устаревший вариант) и Control.Exception (современный вариант), поэтому подключение обоих модулей (например, для использования пре- дикатов исключений ввода-вывода) требует скрывающего или квалифицированного импорта либо же, как в предыдущем при- мере, явного указания импортируемых функций.

 
 

Итак, исключение, произошедшее в действии ввода-вывода countLines, но не по причине отсутствия файла, будет перехвачено и перевызвано в обработчике:

$ ./linecount dont_exist.txt Файл не существует!

$ ./linecount norights.txt

 
 

linecount: noaccess.txt: openFile: permission denied (Permission denied)

Существует несколько предикатов, предназначенных для опре- деления вида исключения ввода-вывода:

® isAlreadyExistsError (файл уже существует);

® isDoesNotExistError (файл не существует);


 

® isAlreadyInUseError (файл уже используется);

® isFullError (не хватает места на диске);

® isEOFError (достигнут конец файла);

® isIllegalOperation (выполнена недопустимая операция);

® isPermissionError (недостаточно прав доступа).

 
 

Пользуясь этими предикатами, можно написать примерно та- кой обработчик:

handler :: IOException -> IO () handler e

| isDoesNotExistError e = putStrLn "Файл не существует!"

| isPermissionError e = putStrLn "Не хватает прав доступа!"

| isFullError e = putStrLn "Освободите место на диске!"

| isIllegalOperation e = putStrLn "Караул! Спасите!"

 
 

| otherwise = ioError e

Убедитесь, что вы перевызываете исключение, если оно не под- ходит под ваши критерии; в противном случае ваша программа иног- да будет «падать» молча, что крайне нежелательно.

 
 

Модуль System.IO.Error также экспортирует функции, которые позволяют нам получать атрибуты исключения, например дескрип- тор файла, вызвавшего исключение, или имя файла. Все эти фун- кции начинаются с префикса ioe; их полный список вы можете найти в документации. Скажем, мы хотим напечатать имя файла в сообщении об ошибке. Значение fileName, полученное при помо- щи функции getArgs, напечатать нельзя, потому что в обработчик передаётся только значение типа IOException и он не знает ни о чём другом. Функция зависит только от своих параметров. Но мы можем вызвать функцию ioeGetFileName, которая по переданному ей ис- ключению возвращает Maybe FilePath. Функция пытается получить из значения исключения имя файла, если такое возможно. Давайте изменим обработчик так, чтобы он печатал полное имя файла, из- за которого возникло исключение (не забудьте включить функцию ioeGetFileName в список импорта для модуля System.IO.Error):

handler :: IOException -> IO () handler e

| isDoesNotExistError e = case ioeGetFileName e of

Just fileName -> putStrLn $ "Файл " ++ fileName ++

" не существует!"


 

Nothing -> putStrLn "Файл не существует!"

| otherwise = ioError e

 
 

where fileName = ioeGetFileName e

В охранном выражении, если предикат isDoesNotExistError вер- нёт значение True, мы использовали выражение case,чтобы вызвать функцию ioeGetFileName с параметром e; затем сделали сопоставле- ние с образцом по возвращённому значению с типом Maybe. Выра- жение case часто используется в случаях, когда вам надо сделать со- поставление с образцом, не создавая новую функцию. Посмотрим, как это сработает:

$ ./linecount dont_exist.txt

 
 

Файл dont_exists.txt не существует!

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

main = do

 
 

action1 `catch` handler1 action2 `catch` handler2 launchRockets

Функция action1 использует функцию handler1 в качестве обработчика, а функция action2 использует handler2. Функция launchRockets не является параметром функции catch, так что любое сгенерированное в ней исключение обрушит нашу програм- му, если только эта функция не использует try или catch внутри себя для обработки собственных ошибок. Конечно же, action1, action2 и launchRockets – это действия ввода-вывода, которые «склеены» друг с другом блоком do и, вероятно, определены где-то в другом месте. Это похоже на блоки try–catch в других языках: вы можете поместить всю вашу программу в один блок try–catch или защищать отдельные участки программы и перехватывать различные исклю- чения для разных участков.


 





Последнее изменение этой страницы: 2017-02-17; Нарушение авторского права страницы

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 18.234.255.5 (0.012 с.)