Попытки разбить длинный метод на 'много маленьких' не всегда заканчиваются успешно... и регулярно возникают вопросы: 'почему так?' и 'что с этим делать?'
Критерии успешности
(помимо работоспособности метода)1. Понятность алгоритма работы.
2. Понятность алгоритма для 'непосвященного' человека.
3. Понятность алгоритма с первого, ну в крайнем случае со второго, раза.
Под словом 'алгоритм' понимается выполняемые действия.
Исходя их этих критериев, получил три правила-рекомендации:
1. Алгоритм на экран
Мэтры утверждают, что длинна метода должна быть до 5, ну максимум до 10 строк. Но в условиях наших вечно-сорванных сроков и слабо грамотных спецов, средняя длинна методов в 5 строк кажется недостижимым идеалом.Гораздо реальным может служить размер экрана (монитора). А точнее размер области редактирования в нормальном режиме (например eclipse, меню сверху и консоль снизу).
На моем ноутбуке это примерно 25-30 строк. Очень желательно чтобы в эти 25 строк вошла сигнатура и обрамляющие скобки (дабы было видно границы метода и параметры). Поэтому получаем что то около 15-20 строк кода.
2. 'Правильное' выделение методов
Пример: есть метод. Его задача вытащить какие-то данные из БД, что то с этими данными сделать и сохранить результат.public void doOperation(Object someCriteria){
SQLQuery selectQuery = session.createSQLQuery("");
selectQuery.setParameter("criteria", someCriteria);
selectQuery.addScalar("value", Hibernate.LONG);
List<Long> values = selectQuery.list();
long summ = 0;
for (Long value: values){
summ +=value;
}
long avg = summ / values.size();
SQLQuery insertQuery = session.createSQLQuery("");
insertQuery.setParameter("date", Calendar.getInstance());
insertQuery.setParameter("summ", summ);
insertQuery.setParameter("avg", avg);
}
В смысл вдумываться не надо - его там нету. Гораздо интересней подумать как не надо бы разбивать этот метод (но как часто это получается).2.1. Метод должен отображать шаги одного 'логического уровня'
Например, в данной задаче, можно выдумать несколько уровней детализации:Уровень 1. 'Достать данные', 'обработать данные' и 'сохранить результат'
Уровень 2. Детализация каждого шага из предыдущего уровня. Например: для 'достать данные' это a)'создать запрос', b)'подготовить запрос' и c) 'выполнить запрос'. Для 'обработать данные' это a) 'найти сумму' и b) 'вычислить среднее арифметическое'.
Уровень 3, 4, 5 и далее на сколько хватит желания и опыта в системном программировании :)
Вообщем неплохо было-бы сделать так, чтобы читатель начиная разбирать публичный метод, сразу видел шаги первого уровня, а потом уже углублялся в детали (если это нужно).
Поэтому, наверное не стоит выделять первые 3 строки, вот так:
public void doOperation(Object someCriteria){
SQLQuery selectQuery = createSelectQuery(someCriteria);
List<Long> values = selectQuery.list();
Results result = calculateResult(values);
...
Разумнее было-бы написать нечто вроде:public void doOperation(Object someCriteria){
List<Long> values = extractData(someCriteria);
Results result = calculateResult(values);
...
2.2. Акцент на главном
Наверное не стоит выделять только код с вычислением результата. Т.е. как то так:public void doOperation(Object someCriteria){
SQLQuery selectQuery = session.createSQLQuery("");
selectQuery.setParameter("criteria", someCriteria);
selectQuery.addScalar("value", Hibernate.LONG);
List<Long> values = selectQuery.list();
Results result = calculateResult(values);
SQLQuery insertQuery = session.createSQLQuery("");
insertQuery.setParameter("date", Calendar.getInstance());
insertQuery.setParameter("summ", result.getSumm());
insertQuery.setParameter("avg", result.getAvg());
}
Получается, что мы показываем как мы лихо обращаемся с классами Hibernate, а какие-то там операции бизнес логики скромно уходит на второй план.2.3. Имена
Ну насчет этого уже много было сказано и написано. Вообщем, как то так оно наверное повеселее было-бы:public void doOperation(Object year){
List<Long> monthlyIncomes= loadMonthlyIncomesForYear(year);
Results report = prepareAnnualReport(monthlyIncomes);
saveReport(report);
}
3. Малая глубина вложенности
В большинстве случаев интересно что делает метод и некоторые сведения как он это делает. Эту информацию можно почерпнуть из а) сигнатуры б) JavaDoc комментариев (если они есть) в) заглянув в реализацию метода.Возмем наихудший из этих вариантов - человек глядя на сигнатуру (и не найдя комментариев) не понял что делает doOperation(Object) и начинает копаться в реализации. Очень хорошо, если при прочтении реализации doOperation(Object) все становится понятно. А если любопытство не удовлетворено? Тогда начинается изучение всех вложенных методов, а потом вложенных во вложенные и т.д.
Субъективные ощущения: если приходится изучать 'нечто' с глубиной вложенности более 2-4, то общая картина теряется.
Поэтому очень хочется, чтобы:
- либо стек вызовов был не глубже 2-3(по крайней мере в границах класса)
- либо имелись внятные сигнатуры и не-было необходимости изучать внутренности метода для понимания того, что он делает.
4. 'Обратная связь':)
5-10 сток в Java вообще не достижимый идеал. т.е. очень здорово, когда получается делать такие методы. и таки часто получается, но есть масса нюансов - от заполнения Map параметрами среды исполнения REST сервиса, до инициализации объекта-холдра в 20+ полей.
ОтветитьУдалитьтак что 5-10 строк в методе, 15-20 методов в классе, 3-5 параметров в функции - это все очень круто, равно как и свое исключение каждой исключительной ситуации. но эти правила можно и нужно нарушать в пользу читаемости, логической связности и семантической понятности кода. а еще не нужно плодить лишних сущностей - это уже старая добрая бритва Оккама
а в остальном - все правильно сделал (С)
Угу, все правильно.
ОтветитьУдалить