В видео cppcon 2017 года мне попался разговор Клауса Иглбергера, который был озаглавлен «Освободи свои функции!».
В этом выступлении докладчик рассказал о том, как переключение на бесплатные функции может
упростить процесс тестирования частных методов (см. в 19:00). Идея в том, что вы тянете
закрытый метод вне класса (вы делаете его свободной функцией), и он становится тестируемым.
Сначала я нашел идею интересной, но потом
чем больше я думал об этом, тем меньше понимал, как это на самом деле должно работать. Например,
допустим, у меня есть следующий (фиктивный) класс:
class SomeClass
{
public:
SomeClass();
~SomeClass();
void someTask();
private:
void someComplexTask();
void someOtherComplexTask();
};
void SomeClass::someTask()
{
someComplexTask();
someOtherComplexTask();
}
// private tasks implementations...
затем someComplexTask()
а также someOtherComplexTask()
это частные методы. Это означает, что они
являются деталями реализации, то есть они могут быть вызваны только внутри SomeClass
(или друзья). Это
Мне кажется, что если вы сделаете их свободными функциями, да, они станут тестируемыми, но они больше не будут
частный, или просто детали реализации, специфичные для SomeClass
, На самом деле, они могут быть вызваны из любого места в коде …
Поэтому мой вопрос таков: почему аргумент мистера Иглбергера верен?
Это явный признак того, что у вас есть недостаток дизайна. Если у тебя есть
частная функция, которую вы должны проверить, и вы должны наклониться назад
чтобы заставить это работать тогда что-то не так. Вы что-то пропустили.
Ваш дизайн не работает.
Его цель — не просто сделать частные функции бесплатными. Он не говорит: «получить все ваши личные функции и сделать их бесплатными функциями». Он говорит, что функциональность, которую необходимо протестировать, не должна быть деталью реализации, потому что если вам нужно протестировать ее, это означает, что функциональность полезна.
Пожалуйста, обратите пристальное внимание на преобразование, которое он делает к коду:
исходный код:
class X
{
public:
void doSomething( ... ) {
...
resetValues();
...
}
...
private:
void resetValues() {
for( int& value : values_ )
value = 0;
}
std::vector<int> values_;
};
Он тянет resetValues
снаружи X
но это заставляет его работать на std::vector<T>
, не на X
:
void resetValues( std::vector<int>& vec )
{
for( int& value : vec )
value = 0;
}
Сейчас resetValues
это функциональность, которая может быть повторно использована и протестирована Поскольку это действительно не имеет ничего общего с X
, но с сбросом всех значений вектора это допустимый дизайн, чтобы сделать его свободной функцией вместо приватной X
метод.
Мне нравится, как Ранн Лифшиц выразил это в своем комментарии:
Я думаю, что лучший способ пойти сюда, это понять, что некоторые частные
функции, на самом деле, являются общими функциями полезности
Я тоже смотрел видео. Но у меня есть несколько разногласий.
1- Требует ли ваш метод доступа к полям? Если нет, то это не относится к классу. Но если это так, им нужны поля. Свободные функции не имеют доступа к полям, если вы не передадите их в качестве аргументов функции. Пожалуйста, учтите, что бесплатные функции не должны рассматриваться как публичные функции.
2- Не все должны быть свободными функциями. Но это хорошая практика — избегать класть все в класс, когда они не нужны.
3- Частные функции не часто должны быть проверены. Но если вы настаиваете, вы можете выполнить такие действия, как недействительный взлом (который не всегда работает, как указано в комментариях):
#define class struct
#define private public
#define protected public
#include "library.h"
#undef class
#undef private
#undef protected
Освобождение вашей функции аккуратнее, но не более осуществимо.