Автоматическое исправление для кодовой базы Java только с общим `catch (Exception e)`

У меня есть большая кодовая база, в которой много-много экземпляров

try {
   // attempt to do something important
} catch (Exception e) {
   // do something lame
}

антипаттерн. Около 700 экземпляров. Мне интересно, есть ли инструмент (например, IDE или что-то в этом роде), который мог бы волшебным образом и массово заменить все эти вещи следующим уровнем специализированных блоков catch. Что-то, что применит массовый рефакторинг, аналогичный IntelliJ или Eclipse "wrap with try/catch" так, как показано ниже. ПРИМЕЧАНИЕ. Я не говорю, что все 700 известны как IOException; это всего лишь пример, предполагающий, что единственным типом исключения, созданным в конкретном случае, был IOException).

Рефакторинг, который я ищу, - это то, что выполняет тот же семантический анализ блока try{}, что и IntelliJ/Eclipse, чтобы обеспечить быстрое исправление "обернуть с помощью catch" или "добавить в предложение throws":

try {
   // attempt to do something important
   // where an IOException is thrown
} catch (IOException e) {
   // still do something lame
}

Я понимаю, что это не «решает» проблему, но избавляет от пошаговой ручной археологии.

Идеи?

Связанный вопрос по обнаружение общего антипаттерна исключения.


person andersoj    schedule 28.03.2012    source источник
comment
Даже если бы это было возможно, что бы вы добавили в блок catch?   -  person Miserable Variable    schedule 29.03.2012
comment
Что бы вы хотели видеть в блоке catch и с какой целью вы это делаете? Вам все равно понадобится блок catch Exception или вы меняете семантику.   -  person Peter Lawrey    schedule 29.03.2012
comment
Вы уверены, что менять сразу все 700 экземпляров — это хорошая идея? Не похоже, что это можно безопасно сделать бездумно и без тестирования. Одна из причин того, что это антишаблон, заключается в том, что он подавляет исключения, о которых вы хотите знать и с которыми нужно иметь дело; если вы устраните антипаттерн сразу везде, вам может внезапно понадобиться иметь дело с большим, чем вы ожидаете!   -  person ruakh    schedule 29.03.2012
comment
@ruakh: Я почти уверен, что менять все экземпляры сразу, волей-неволей, плохо. Я также уверен, что оставлять как есть плохо, потому что семантика не понята.   -  person andersoj    schedule 29.03.2012
comment
@MiserableVariable: транспонирует существующее содержимое блока catch.   -  person andersoj    schedule 29.03.2012
comment
@PeterLawrey: Поскольку вокруг нет единого специализированного блока перехвата исключений, вы думаете, меня беспокоит семантика? Вы, наверное, думаете, что у меня тоже есть тесты.   -  person andersoj    schedule 29.03.2012
comment
@andersoj: я не предлагаю оставить все как есть. Я предлагаю изменять отдельные экземпляры по отдельности и тестировать, чтобы убедиться в отсутствии новых проблем. Что произойдет, если некоторые из этих catch блоков на самом деле перехватят огромное количество NullPointerException? Вы не хотите изменять его, чтобы позволить NullPointerException пройти без также устранения основной проблемы (проблем).   -  person ruakh    schedule 29.03.2012
comment
Я не понимаю, почему sed не сработает для этого. Все, что вам нужно, это (опасный) глобальный поиск/замена.   -  person jahroy    schedule 29.03.2012
comment
Справедливые точки, все. Это плохая ситуация. Стремление избежать тяжелой работы по выяснению того, какие проверенные исключения необходимо перехватывать.   -  person andersoj    schedule 29.03.2012
comment
Кто-то может опубликовать канонический ответ «Не делай этого», если захочет. Согласитесь, это справедливое замечание.   -  person andersoj    schedule 29.03.2012
comment
Если поведение не отличается, это не имеет никакого значения AFAICS   -  person Peter Lawrey    schedule 29.03.2012
comment
@jahroy: если ваш sed не умнее моего, он не сможет определить, какие проверенные исключения выбрасываются в блоке try{}   -  person andersoj    schedule 29.03.2012
comment
Я думаю, что это можно сделать безопасно (я не знаю никаких инструментов, которые это делают). Просто замените try { some code that could throw A,B,C} catch ( Exception ) { lame } на try { some code that could throw A,B,C } catch ( A, B, C, RuntimeException ) { lame } Это бесполезно, кроме как в качестве первого шага в улучшении кода.   -  person emory    schedule 29.03.2012
comment
@Peter Lawrey: Это не сразу изменило бы поведение. Тем не менее, это был бы первый шаг к ее реальному исправлению, установив специализированные типы исключений, которые затем можно было бы выполнить тяжелую работу по выяснению того, что следует делать.   -  person andersoj    schedule 29.03.2012
comment
Я думал, что мы предполагаем, что выбрасываются только IOExceptions. Я неправильно понял это утверждение. Извини.   -  person jahroy    schedule 29.03.2012
comment
@emory: опять же, это не повторяющийся шаблон кода, который может выполнять поиск/замену. Это 700 в основном разных фрагментов. Все они делают catch (Exception e) { Log(e); } Поэтому не могут выполнять поиск/замену без некоторого семантического анализа содержимого блока try{}.   -  person andersoj    schedule 29.03.2012
comment
Можете ли вы вместо этого изменить поведение Log? Я бы изменил только то исключение, которое вы действительно видите в журналах.   -  person Peter Lawrey    schedule 29.03.2012
comment
@andersoj - я согласен, что это не повторяющийся шаблон кода, поэтому я и написал, что не знаю инструмента, который это сделает. Я хотел сказать, что вы могли бы сделать это безопасно, особенно если инструмент добавил RuntimeException в конце.   -  person emory    schedule 29.03.2012
comment
@Peter Lawrey: Цель этих усилий — начать тяжелую работу по анализу каждого предложения catch и сделать что-то менее хромое, чем Log e. По крайней мере, если я смогу избежать ручного труда 700, какие проверенные исключения есть в блоке? вопрос и перейти к хорошо я получил IOException, что теперь правильно делать? вопрос, на который я действительно хочу поставить ментальные циклы.   -  person andersoj    schedule 29.03.2012
comment
@emory: неправильно понял, извини.   -  person andersoj    schedule 29.03.2012
comment
Вы когда-нибудь видели эти исключения? Что вы на самом деле видите в журнале? Я бы начал с них, у вас должна быть трассировка стека для них, чтобы вы могли видеть, где они происходят.   -  person Peter Lawrey    schedule 29.03.2012
comment
@Peter Lawrey: Да, они время от времени видят эти исключения. Однако ведение журнала плохое, и это не повторяется. Идеальный подход здесь состоит в том, чтобы постепенно получать модульное тестирование каждого модуля и выяснять, как вызывать исключения... но идеалы не всегда практичны.   -  person andersoj    schedule 29.03.2012


Ответы (2)


Учитывая, что проблема:

Найдите все блоки вида:

 try { ... }
 catch (Exception e) { ... }

с

 try { ... }
 catch ( T1 e ) { ... }
 ....
 catch ( Tn e ) { ... }

для всех T1, ... Tn e система преобразования программ, которая может выполнять изменения кода и причины возникновения исключений, кажется решением.

Наш инструментарий реинжиниринга программного обеспечения DMS с его Java Front End, скорее всего, может сделать это.

Вам нужен анализатор, который может вычислить набор исключений, генерируемых внутри основного блока. Вероятно, вы также захотите классифицировать эти исключения в соответствии с иерархией объектов; если Tm является специализацией Tn, вы можете создать обработчик для Tm и Tn как вложенных попыток. DMS имеет полный анализ типов, поэтому он может определять типы в коде и выбрасывать блок кода. Чтобы вычислить иерархию объектов для исключений, нужно было бы написать собственный код DMS.

Вам нужен граф вызовов, чтобы можно было распространять исключения, найденные в графе вызовов, на пробный сайт. У нас это есть, но для Java 1.7 требуется доработка.

Вам нужен преобразователь для исправленного кода. Я думаю, вам нужно беспокоиться о цепных уловах, чтобы справиться с общим случаем. С DMS это должно выглядеть так:

rule specialize_catch(b_try: block,
      E: qualifed_identifer, e: IDENTIFIER, b_recover: block,
      c_pre: catches, c_post: catches):
   statement -> statement
 " try { \b_try } 
   \c_pre
   catch ( \E \e ) { \b_recover }
   \c_post "
 -> 
  " try { \b_try }
    \c_pre 
    catch ( \least_specialized\(\E\,\b_try\,\c_pre\) \e ) { \b_recover }
    catch ( \E e ) { \b_recover }
    \c_post "
    if exists_specialized_exception(E,b_try,c_pre);

Преобразуемый синтаксис - это код в *мета*кавычках "...", чтобы отделить его от синтаксиса правил перезаписи. Правило перезаписи состоит из нескольких частей. Он указывает имя (у нас часто их сотни). Он предоставляет набор именованных заполнителей (b_try, E, b_recover, ...), представляющих определенные именованные синтаксические формы на целевом языке (в данном случае Java) и написанные в чистом виде без метакавычек и в экранированной (обратной косой) форме. внутри метакавычек. c_pre — это имя серии конструкций catch; мы можем сделать это, потому что «поймать» образуют ассоциативный список в аннотации, и аналогично для c_post. Он обеспечивает вызовы метафункций (например, наименьший_специализированный, существующий_специализированный_исключение), которые могут вызывать пользовательский механизм DMS для вычисления результатов (булевы значения или новый синтаксис [деревья]). Вы заметите, что в вызове метафункции наименьший_специализированный даже синтаксис для вызова метафункции экранирован (например, запятые и круглые скобки), поскольку они не являются частью языка Java; вне кода Java такие вызовы метафункций не нуждаются в экранировании. И самое главное, у него есть левая часть («сопоставить это», привязка метапеременных) и правая часть («заменить этим», если условие правила истинно.

Метафункции наименьший_специализированный и существующий_специализированный будут вычислять для исключений, которые может генерировать основной блок кода b_try, а также для тех, которые обрабатываются существующими перехватами c_pre и текущим типом исключения E, наиболее общим исключением выше c_pre и ниже E, а также для нового перехвата блок вставлен. Если таковых не существует, то if завершается ошибкой, и вставки исключений больше не выполняются. Возможно, вам понадобятся дополнительные преобразования для деклонирования дублированного блока b_recover.

Я, очевидно, не реализовал это и, возможно, не продумал это полностью. Но я вижу в этом путь к возможному решению. YMMV.

Я скажу, что для 700 экземпляров, вероятно, довольно маргинально делать это с DMS. Если вам лично потребовалось по 10 минут на каждую часть (редактирование, компиляция, упс...), то это 7000 минут или ~~ 100 часов, около 2 недель. Я сомневаюсь, что вы могли бы настроить DMS, чтобы сделать это так быстро, особенно если вы никогда не делали этого раньше. (В моей компании есть опытные пользователи DMS, для которых это, вероятно, приемлемый срок).

Но утверждается, что инструмент, вероятно, существует.

person Ira Baxter    schedule 28.03.2012

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

  1. Определить новое исключение

class NeverThrown extends RuntimeException {}

  1. Глобальная замена catch (Exception e) на catch (NeverThrown e)

  2. Скомпилировать все файлы

  3. Для каждого вхождения unreported exception .... must be caught... идентифицируйте исключение и добавьте его в следующий блок catch, если оно еще не добавлено, например. заменить catch (NeverThrown e) на catch (NeverThrown, IOException e)

  4. Удалите все вхождения NeverThrown, и удалите определение класса.

Конечно, большие интеллектуальные усилия заключаются в том, чтобы определить, что делать вместо «чего-то хромого». То, что вы не сможете сделать оптом, каждый случай придется осматривать индивидуально. Если бы я был на вашем месте, я бы остановился на шаге № 3 и просмотрел ошибки, пытаясь понять, как обрабатывать каждое неперехваченное исключение.

Или вы можете полностью отказаться от проверенных исключений и удалить все блоки try и catch, добавив throws Exception после каждого метода :)

person Miserable Variable    schedule 28.03.2012
comment
Не кажется ли вам, что можно было бы использовать некоторую магию механизма факторинга Eclipse, возможно, с небольшими изменениями, вместо того, чтобы полагаться на предупреждения компилятора, как в шаге (3)? - person andersoj; 29.03.2012
comment
@andersoj, в этом я не уверен. Я знаю, что рефакторинг можно сохранить как сценарий и применить повторно, но я не знаю, сохранена ли команда add missing exception to outer try block или add IOException to outer try block. - person Miserable Variable; 29.03.2012