о функциях (программистское)
Jan. 5th, 2010 03:37 pmЭта запись будет интересна только программистам.
Предлагаю вопрос о том, как обустраивать код. Представьте себе, что у вас есть функция foo() (неважно, на каком языке; может, это метод, а не функция - неважно), вся работа которой - вызвать какие-то другие четыре функции A(), B(), C(), D(), которые расположены в других файлах и которые и делают всю основную работу. foo() должна приготовить для них аргументы, передать результаты работы A() в B(), и так далее. Логика foo() выглядит очень просто:
1. Вызвать A().
2. Вызвать B().
3. Вызвать C().
4. Если результат, который вернула C, интересный (условие на одну строчку), вызвать D().
Каждый из этих пунктов занимает где-то 5-10 строк: кроме самого вызова, из-за того, что он готовит правильные аргументы, проверяет, что функция вернула, плюс комментарий, плюс в нескольких местах пишет что-то в лог - в общем, всякие мелочи, но накапливается. Общий размер функции foo() - 40 строк.
Есыь предложение разбить функцию foo(), выделив каждый из логических кусков в отдельную функцию - скажем, doA(), doB() итд. - чтобы foo() только их вызывала. Противник этого преедложения говорит, что на данный момент нет никаких оснований считать, что кому-то еще понадобиться вызывать doA(), doB() итд., кроме foo(). Кроме того, тестировать отдельно doA(), doB() итд. тоже не надо - у главных функций A(), B() итд. есть свои тесты, и у foo() будет свой тест. С другой стороны, сторонник этого предложения, соглашаясь с этим, говорит, что все равно foo() слишком длинна, и что раз есть возможность выделить ее части в отдельные функции, правильным будет сделать этот рефакторинг. После него код будет читабельнее, понятнее, и удобнее для поддержки.
Как вы считаете? И какие аргументы выдвинули бы в поддержку своей точки зрения?
Предлагаю вопрос о том, как обустраивать код. Представьте себе, что у вас есть функция foo() (неважно, на каком языке; может, это метод, а не функция - неважно), вся работа которой - вызвать какие-то другие четыре функции A(), B(), C(), D(), которые расположены в других файлах и которые и делают всю основную работу. foo() должна приготовить для них аргументы, передать результаты работы A() в B(), и так далее. Логика foo() выглядит очень просто:
1. Вызвать A().
2. Вызвать B().
3. Вызвать C().
4. Если результат, который вернула C, интересный (условие на одну строчку), вызвать D().
Каждый из этих пунктов занимает где-то 5-10 строк: кроме самого вызова, из-за того, что он готовит правильные аргументы, проверяет, что функция вернула, плюс комментарий, плюс в нескольких местах пишет что-то в лог - в общем, всякие мелочи, но накапливается. Общий размер функции foo() - 40 строк.
Есыь предложение разбить функцию foo(), выделив каждый из логических кусков в отдельную функцию - скажем, doA(), doB() итд. - чтобы foo() только их вызывала. Противник этого преедложения говорит, что на данный момент нет никаких оснований считать, что кому-то еще понадобиться вызывать doA(), doB() итд., кроме foo(). Кроме того, тестировать отдельно doA(), doB() итд. тоже не надо - у главных функций A(), B() итд. есть свои тесты, и у foo() будет свой тест. С другой стороны, сторонник этого предложения, соглашаясь с этим, говорит, что все равно foo() слишком длинна, и что раз есть возможность выделить ее части в отдельные функции, правильным будет сделать этот рефакторинг. После него код будет читабельнее, понятнее, и удобнее для поддержки.
Как вы считаете? И какие аргументы выдвинули бы в поддержку своей точки зрения?
no subject
Date: 2010-01-05 01:44 pm (UTC)1. Наглядность.
2. Уменьшается сцепление ("coupling"). Кроме того, пространства имён функций doA(), doB()... явным образом сделаны независимыми.
3. Явно ограничивается время существования ненужных временных объектов, которые понадобилось создавать для выполнения doA(), doB()... Ну и стековый фрейм экономится заодно.
3.
no subject
Date: 2010-01-05 01:45 pm (UTC)no subject
Date: 2010-01-05 01:46 pm (UTC)Я сам в таких случаях не разбиваю, но не уверен, что это хорошо.
Мне кажется, что для повышения читабельности в таком случае можно воспользоваться комментариями и пробелами, искуственно выделяющими вызов А, В, С и Д.
no subject
Date: 2010-01-05 01:47 pm (UTC)no subject
Date: 2010-01-05 01:48 pm (UTC)1. doA-B-C могут где то еще пригодится
2. Обработка данных содержит значимый елемент логики, а не просто перекладывание яиц из листа в массив
То я бы сделал отдельные функции, в обратном случае можно, а иногда даже нужно оставить все вместе.
no subject
Date: 2010-01-05 01:48 pm (UTC)no subject
Date: 2010-01-05 01:48 pm (UTC)no subject
Date: 2010-01-05 01:54 pm (UTC)Логичнее делать сложный инструмент и учиться настраивать его чем разбираться с кучей мелких и простых
no subject
Date: 2010-01-05 01:59 pm (UTC)"жениться вам надо, барин". :)
no subject
Date: 2010-01-05 01:59 pm (UTC)С другой стороны, если эти 10 строк - это логи и ассерты и это повторяется в миллионе мест - можно подумать об AOP, так было бы изящнее.
no subject
Date: 2010-01-05 02:01 pm (UTC)no subject
Date: 2010-01-05 02:01 pm (UTC)no subject
Date: 2010-01-05 02:04 pm (UTC)40 строк - это много (лично меня пугают функции длиннее 20 строк :-). В этом месиве не будет четко видно, что вся суть функции - это последовательный вызов четырех остальных.
no subject
Date: 2010-01-05 02:04 pm (UTC)no subject
Date: 2010-01-05 02:05 pm (UTC)таков критерий.
а то что там длинный или короткий получается код -- это категорически неважно.
важна лишь адекватность реальному миру. Есть естественное деление которое можно отобразить функцией -- выделяем её. всё.
no subject
Date: 2010-01-05 02:05 pm (UTC)Но ради одного только размера функции я бы не стал добавлять лишний уровень.
no subject
Date: 2010-01-05 02:06 pm (UTC)no subject
Date: 2010-01-05 02:06 pm (UTC)Это достаточный аргумент. Совершенно не нужно чтобы doA() была интересна еще кому-то, кроме foo(), но если есть возможность вынести часть функционала в отдельную процедуру и дать ей имя - то почему бы и нет?
public function pushMessageAction($message) { $userModel = new Model_User(); $target = $userModel->findByNickname($message->['nickname']); if (!$target) { return false; } ... }сравним:
public function pushMessageAction($message) { if ($this->incorrectNickname($message)) { return false; } ...(пример выдумал тут же)
(да, я знаю что $target и $userModel могут потребоваться еще раз тут же, в pushMessageAction())
(да, выйгрышь - только в читаемости кода)
(да, я считаю, что это - достаточно весомый аргумент в пользу рефакторинга)
no subject
Date: 2010-01-05 02:06 pm (UTC)no subject
Date: 2010-01-05 02:07 pm (UTC))
Date: 2010-01-05 02:08 pm (UTC)no subject
Date: 2010-01-05 02:09 pm (UTC)Если такой опасносте нет, то не все ли равно, как делать? Пусть люди самовыражаются, как хотят.
no subject
Date: 2010-01-05 02:10 pm (UTC)no subject
Date: 2010-01-05 02:12 pm (UTC)пожалуй, плюсану.
no subject
Date: 2010-01-05 02:16 pm (UTC)Как раз таки для читабельности удобнее если всё оставить в одной небольшой функции, не разбивая. Нередко вижу код с огромным кол-вом никому не нужных мелких - на одну-пять строк кода, только ради "разбивки" одного метода. Всё зависит от ситуации, но в данном случае я бы оставила - чистоты кода ради.