Программист-прагматик. Путь от подмастерья к мастеру

ОглавлениеДобавить в закладки К обложке

25Балансировка ресурсов

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

Билл Косби, Отцовство

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

Однако многие разработчики не имеют сколько-нибудь завершенного плана, касающегося распределения и освобождения ресурсов. Поэтому предлагается простая подсказка:

Подсказка 35: Доводите до конца то, что начинаете

В большинстве случаев эту подсказку применить несложно. Она всего лишь означает, что подпрограмма или объект, который назначает ресурс, должен нести ответственность за освобождение этого ресурса. Использование указанной подсказки можно рассмотреть на примере неудачной программы – приложения, открывающего файл, считывающего из него информацию о заказчике, обновляющего поле и записывающего результат обратно в файл. Чтобы сделать пример более наглядным, мы опустили процедуру обработки ошибок.

void readCustomer(const char *fName, Customer *cRec) {

cFiie = fopen(fName, "r+");

fread(cRec, sizeof(*cRec), 1, cFile);

}

void writeCustomer(Customer *cRec) {

rewind(cFile);

fwrite(cRec, sizeof(*cRec), 1; cFile);

fclose(cFile);

}

void updateCustomer(const char *fName, double newBalance) {

Customer cRec;

readCustomer(fName, &cRec);

cRec.balance = newBalance;

writeCustomer(&cRec);

}

На первый взгляд, подпрограмма updateCustomer выглядит довольно прилично. Похоже, что она реализует нужную нам логику – считывает запись, обновляет баланс и осуществляет запись обратно в файл. Однако, за внешним приличием и скрывается главная проблема. Подпрограммы readCustomer и writeCustomer тесно связаны между собой [27] – они совместно используют глобальную переменную cFile. Подпрограмма readCustomer открывает файл и сохраняет указатель файла в переменной cFile, а подпрограмма writeCustomer использует сохраненный указатель для закрытия файла по окончании работы. Эта глобальная переменная даже не появляется в подпрограмме updateCustomer.

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

void updateCustomer(const char *fName, double newBalance) {

Customer cRec;

readCustomer(fName, &cRec);

if (newBalance >= 0.0) {

   cRec.balance = newBalance;

   writeCustomer(&cRec);

  }

}

Во время испытания все выглядит замечательно. Однако когда программа вводится в эксплуатацию, она «падает» через несколько часов, выдавая сообщение "Слишком много открытых файлов". Поскольку подпрограмма writeBalance в ряде случаев не вызывается, то и файл не закрывается.

У этой проблемы есть весьма неудачное решение – использовать специальный оператор выбора в подпрограмме updateCustomer:

void updateCustomer(const char *fName, double newBalance) {

Customer cRec;

readCustomer(fName, &cRec);

if (newBalance»= 0.0) {

  cRec.balance = newBalance;

  writeCustomer(&cRec);

}

else

fclose(cFile);

}

Это устраняет проблему – файл закроется независимо от нового значения баланса, но эта процедура означает, что теперь связанными оказываются три подпрограммы (через глобальную переменную cFile). Мы попадаем в ловушку, и если мы продолжаем действовать в том же духе, то все полетит под откос.

Подсказка "Доводите до конца то, что начинаете" говорит нам о том, что в идеале подпрограмма, которая назначает ресурс, обязана его и освобождать. Мы можем применить ее в данном случае, осуществляя небольшую реорганизацию программы:

void readCustomer(FILE *cFile, Customer *cRec) {

fread(cRec, sizeof(*cRec), 1, cFile);

}

void writeCustomer(FILE *cFile, Customer *cRec) {


Логин
Пароль
Запомнить меня