14 August 2011

Про абстракции и метод рефакторинга «Extract method»

C++
Sandbox
Абстракции чрезвычайно важны в программировании и это все знают. Они помогают нам отделить существенные детали чего бы то ни было от несущественных. В идеале они должны выделять только самое главное, эссенцию, без всяких посторонних примесей, минимум характеристик объекта или процесса, но идеал встречается не так уж и часто.

Когда программист пишет код, он оперирует абстракциями.

«Клиент посылает запрос Серверу».

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

client.send(request, server);


У читателя не возникает вопросов по при виде этой строки.

Часто одной строки кода бывает недостаточно и программисты добавляют ещё строчек. Много времени и места занимает, например, валидация входных параметров и обработка возвращаемых функциями значений.


void sendRequest(Request request)
{
        if (server->error() != Server::errorOk)
        {
                // handle
        }

        if (request.userName.empty())
        {
                // handle
        }

        // ещё 100 if-ов

        client.send(request, server);
}


Я так часто вижу подобные if'ы, что воспринимаю их как шум (особенно Yoda-condition). Они безусловно нужны, но… я считаю, что их не должно быть здесь, в этой функции sendRequest, потому что client.send находится на более высоком уровне абстракции чем всякие Server::ErrorState. Эти if'ы — более низкоуровневые абстракции, поэтому их надо спрятать в отдельные методы isServerReady() и isRequestValid(). Тогда код будет такой:


void sendRequest(Request request)
{
        if (!isServerReady())
        {
                // handle
        }

        if (!isRequestValid(request))
        {
                // handle
        }

        // больше нет if'ов

        client.send(request, server);
}


Теперь sendRequest оперирует функциями, находящимися на одном уровне абстракции, поэтому код читается легко и приятно. Бонус — эти методы можно (нужно) повторно использовать вместо копирования if'ов. Я не хочу видеть эти уродливые server->error() != Server::errorOk в функции sendRequest, но я хотел бы их видеть, когда они мне понадобятся. Тогда я посмотрю содержимое isServerReady(). Чуете?

Или вот например. Большой-пребольшой класс. По всем методам разбросаны условия вида


if (niceNamedContainer.empty())
{
// do some logic
}


Когда я вижу код в первый раз, то про if (niceNamedContainer.empty()) я могу только сказать «Если в niceNamedContainer ничего нет». Сразу у меня возникает вопрос, а что это значит? Сам себе отвечаю: в него ничего не положили, где-то в другом месте, где должны были положить, но не здесь. После перелопачивания не менее чем ста процентов кода класса с целью разобраться, как он работает, я понимаю, что это условие на самом деле означает «Данные готовы». Ура. Но я думаю, что было бы совсем не трудно сделать выделение метода isDataReady() на этапе написания класса. Читателю было бы гораздо легче, и не пришлось бы применять телепатические способности, потому что читателю кода в первую очередь нужны высокоуровневые абстракции. Метод isDataReady() это лестница, соединяющая низкий уровень абстракции с высоким. По ней очень удобно спускаться вниз, если надо, а если не надо, то можно и не спускаться. Я думаю, что это сэкономило бы порядочно времени на поддержке чужого, старого, унаследованного кода. А делов-то, применить выделение метода да следить, что бы одна функция работала только с одним уровнем абстракции… ну еще SRP не забывать… ну и Лисков… ну и инкапсуляцию… МакКоннела тоже… чаще вспоминать.

Данные примеры хотя и примитивны, но не так далеки от реальности, как могут показаться. Упор я сделал на if'ы, поскольку условные конструкции формулируют, так сказать, логику работы кода и потому, что иногда в переплетении if'ов довольно трудно разобраться. Тем не менее это далеко не единственное место применения Extract Method.

Ссылки:
ru.wikipedia.org/wiki/Абстракция_данных

Литература:
Steve McConnell. Code Complete: A Practical Handbook of Software Construction.
Robert C. Martin. Clean Code: A Handbook of Agile Software Craftsmanship.
Martin Fowler. Refactoring: Improving the Design of Existing Code
Tags:ООПабстракцияc++рефакторингвыделение методачитаемость кода
Hubs: C++
+67
3.2k 28
Comments 35
Popular right now
C++ Developer
from 130,000 to 180,000 ₽QuadcodeСанкт-Петербург
Разработчик C++
from 90,000 ₽ТакскомМосква
C++ разработчик
from 80,000 ₽TRUSTSOFTКраснодар
Middle C++ developer
from 200,000 ₽Scalable SolutionsRemote job
Разработчик C++/Python
from 120,000 to 170,000 ₽L3 TechnologiesМосква
Top of the last 24 hours