Я хотел бы получить совет по технике, на которую я наткнулся. Это можно легко понять, просмотрев фрагменты кода, но в следующих абзацах я задокументирую это несколько подробнее.
Использование идиомы «Code Sandwich» - обычное дело для управления ресурсами. Привыкнув к идиоме RAII в C ++, я переключился на Java и обнаружил, что мое безопасное для исключений управление ресурсами приводит к глубоко вложенному коду, в котором мне очень трудно справиться с обычным потоком управления.
Очевидно (доступ к данным Java: это хороший стиль кода доступа к данным Java, или это слишком много попыток, наконец?, Уродливый блок попыток в Java io и многое другое) Я не одинок.
Я пробовал разные решения, чтобы справиться с этим:
явно поддерживать состояние программы:
resource1aquired
, _2 _... и условно очищать: _3 _... Но я избегаю дублирования состояния программы в явных переменных - среда выполнения знает состояние, и я не хочу заботиться о нем.обернуть каждый вложенный блок в функции - это еще больше усложнит отслеживание потока управления и приведет к действительно неудобным именам функций:
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
и других).
Но...
Я нигде не нашел упоминания об этой технике. Итак, вот вопрос:
Этот метод действителен? Какие ошибки вы в нем видите?
Большое спасибо.
compensate
реализаций. - person xtofl   schedule 31.08.2011try-finally
- вы могли бы просто принять эту идиому с самим кодом и не беспокоиться о блокеfinally
. Кроме того, многиеCompensators
естественно генерируют исключения (например,SQLException
для закрытия ресурса БД); каждая реализация должна перехватывать и обрабатывать все исключения по отдельности, а не вашfinally
блок, обрабатывающий их последовательно в одном месте. Для них это не обязательно (исключение во время выполнения нарушит их спецификацию, и это может произойти в любое время), поэтому вы просто поощряете копипасту. - person Andrzej Doyle   schedule 31.08.2011scope(exit)
оператор D - person ratchet freak   schedule 31.08.2011