Я хотел бы получить совет по технике, на которую я столкнулся. Это можно легко понять, просмотрев фрагменты кода, но я документирую его несколько подробнее в следующих параграфах.
Использование идиомы "Code Sandwich" является обычным делом для управления ресурсами. Используемый для идиомы С++ RAII, я переключился на Java и нашел мое безопасное управление ресурсами, в результате чего был глубоко вложенный код, в котором мне очень трудно получить контроль над регулярным потоком управления.
По-видимому (доступ к данным java: это хороший стиль кода доступа к данным Java, или он слишком сильно пытается наконец?, Java io уродливый блок 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
и любых других).
Но...
Я не нашел упоминания об этой технике в другом месте. Итак, вот вопрос:
Является ли этот метод действительным? Какие ошибки вы видите в нем?
Большое спасибо.