Я хотел бы получить совет по технике, с которой я столкнулся. Это можно легко понять, взглянув на фрагменты кода, но я документирую это несколько подробнее в следующих параграфах.
Использование идиомы «Code Sandwich» - обычное дело для управления ресурсами. Привыкнув к идиоме RAII в C ++, я переключился на Java и обнаружил, что мое исключительное безопасное управление ресурсами приводит к глубоко вложенному коду, в котором мне действительно трудно получить контроль над регулярным потоком управления.
Видимо ( доступ к данным Java: это хороший стиль кода доступа к данным Java или слишком много попыток в конце концов? , Java и уродливый блок try-finally и многие другие ) Я не одинок.
Я пробовал разные решения, чтобы справиться с этим:
поддерживать состояние программы в явном виде: resource1aquired
, fileopened
... и выполнять условную очистку: if (resource1acquired) resource1.cleanup()
... Но я избегаю дублирования состояния программы в явных переменных - среда выполнения знает состояние, и я не хочу об этом заботиться.
оборачивает каждый вложенный блок в функции - в результате становится еще труднее следить за потоком управления и делает для действительно неудобных имен функций: runResource1Acquired( r1 )
, runFileOpened( r1, file )
, ...
И, наконец, я пришел к идиоме (также концептуально), подкрепленному какой-то исследовательской работой по сэндвичам с кодом :
Вместо этого:
// (pseudocode)
try {
connection = DBusConnection.SessionBus(); // may throw, needs cleanup
try {
exported = false;
connection.export("/MyObject", myObject ); // may throw, needs cleanup
exported = true;
//... more try{}finally{} nested blocks
} finally {
if( exported ) connection.unExport( "/MyObject" );
}
} finally {
if (connection != null ) connection.disconnect();
}
Используя вспомогательную конструкцию, вы можете получить более линейную конструкцию, в которой код компенсации находится рядом с инициатором.
class Compensation {
public void compensate(){};
}
compensations = new Stack<Compensation>();
И вложенный код становится линейным:
try {
connection = DBusConnection.SessionBus(); // may throw, needs cleanup
compensations.push( new Compensation(){ public void compensate() {
connection.disconnect();
});
connection.export("/MyObject", myObject ); // may throw, needs cleanup
compensations.push( new Compensation(){ public void compensate() {
connection.unExport( "/MyObject" );
});
// unfolded try{}finally{} code
} finally {
while( !compensations.empty() )
compensations.pop().compensate();
}
Я был в восторге: независимо от того, сколько исключительных путей, поток управления остается линейным, а код очистки визуально находится рядом с исходным кодом. Кроме того, ему не нужен искусственно ограниченный метод closeQuietly
, что делает его более гибким (т. Е. Не только Closeable
объектов, но также Disconnectable
, Rollbackable
и любых других).
Но ...
Я не нашел упоминаний об этой технике в другом месте. Итак, вот вопрос:
Эта техника действительна? Какие ошибки вы видите в этом?
Большое спасибо.