В моем приложении я имею дело с классами большего размера (более 50 методов), каждый из которых достаточно сложен. Меня не беспокоит сложность, поскольку они все еще просты с точки зрения выделения частей функциональности в меньшие методы и последующего их вызова. Таким образом, число методов становится большим (многие из этих методов являются частными, в частности изолируя части функциональности).
Однако когда я добираюсь до стадии реализации, я обнаруживаю, что теряю счет, какие методы были реализованы, а какие нет. Тогда на этапе связывания я получаю ошибки для нереализованных методов. Это было бы хорошо, но между классами существует много взаимозависимостей, и для того, чтобы связать приложение, мне нужно подготовить ВСЕ. Тем не менее, я бы предпочел пройти один курс, прежде чем перейти к следующему.
По независящим от меня причинам я не могу использовать IDE — только простой текстовый редактор и компилятор g ++. Есть ли способ найти нереализованные методы в одном классе без полной ссылки? Прямо сейчас я буквально выполняю текстовый поиск по сигнатурам методов в файле cpp реализации для каждого из методов, но это занимает очень много времени.
Хотя я не вижу простого способа сделать это без фактической попытки связывания, вы можете получить вывод компоновщика для «неопределенной ссылки на ClassInQuestion ::», которая должна давать вам только строки, связанные с этой ошибкой, для методов данного класса ,
Это, по крайней мере, позволяет избежать просеивания всех сообщений об ошибках всего процесса компоновки, хотя и не препятствует выполнению полной компоновки.
Вы можете добавить заглушку для каждого метода, который вы собираетесь реализовать, и сделать:
void SomeClass::someMethod() {
#error Not implemented
}
С gcc это выводит файл, номер строки и сообщение об ошибке для каждого из них. Таким образом, вы можете просто скомпилировать рассматриваемый модуль и выполнить команду grep для «Не реализовано», не требуя запуска компоновщика.
Хотя вам все равно нужно добавить эти заглушки в файлы реализации, что может быть частью того, что вы пытались обойти в первую очередь.
Вот для чего предназначены модульные тесты и инструменты покрытия тестов: напишите минимальные тесты для всех функций заранее. Тесты на отсутствующие функции не будут ссылаться. Отчет о тестовом покрытии покажет, все ли функции были посещены.
Конечно, это только помогает в некоторой степени, это не 100% доказательство дурака. Хотя ваша методология разработки звучит немного странно: разработка классов один за другим в отдельности не работает на практике: классы, которые зависят друг от друга (и помните: уменьшайте зависимости!), Должны быть в некоторой степени разработаны в слежку. Вы не можете создать полную реализацию для одного класса и перейти к следующему, никогда не оглядываясь назад.
В прошлом я создавал исполняемый файл для каждого класса:
#include "klass.h"int main() {
Klass object;
return 0;
}
Это сокращает время сборки, позволяет сосредоточиться на одном занятии, ускоряет цикл обратной связи.
Это может быть легко автоматизировано.
Я действительно хотел бы уменьшить размер этого класса!
редактировать
Если есть препятствия, вы можете пойти грубой силой:
#include "klass.h"
Klass createObject() {
return *reinterpret_cast<Klass>(0);
}
int main() {
Klass object = createObject();
return 0;
}
Вы можете написать небольшой скрипт, который анализирует файл заголовка для реализаций метода (регулярные выражения сделают это очень просто), а затем сканирует файл реализации для тех же реализаций метода.
Например, в Ruby (для модуля компиляции C ++):
className = "" # Either hard-code or Regex /class \w+/
allMethods = []
# Scan header file for methods
File.open(<headerFile>, "r") do |file|
allLines = file.map { |line| line }
allLines.each do |line|
if (line =~ /(\);)$/) # Finds lines ending in ");" (end of method decl.)
allMethods << line.strip!
end
end
end
implementedMethods = []
yetToImplement = []
# Scan implementation file for same methods
File.open(<implementationFile>, "r") do |file|
contents = file.read
allMethods.each do |method|
if (contents.include?(method)) # Or (className + "::" + method)
implementedMethods << method
else
yetToImplement << method
end
end
end
# Print the results (may need to scroll the code window)
print "Yet to implement:\n"yetToImplement.each do |method|
print (method + "\n")
end
print "\nAlready implemented:\n"implementedMethods.each do |method
print (method + "\n")
end
Кто-то еще сможет рассказать вам, как автоматизировать это в процессе сборки, но это один из способов быстро проверить, какие методы еще не были реализованы.
Ключевое слово delete в c ++ 11 делает свое дело
struct S{
void f()=delete; //unimplemented
};
Если C ++ 11 недоступен, вы можете использовать private как обходной путь
struct S{
private: //unimplemented
void f();
};
С помощью этих двух методов вы можете написать тестовый код в файле .cpp
//test_S.cpp
#include "S.hpp"namespace{
void test(){
S* s;
s->f(); //will trigger a compilation error
}
}
Обратите внимание, что ваш тестовый код никогда не будет выполнен. Пространство имен {} сообщает компоновщику, что этот код никогда не используется вне текущего модуля компиляции (т.е. test_S.cpp) и поэтому будет отброшен сразу после проверки компиляции.
Поскольку этот код никогда не выполняется, вам на самом деле не нужно создавать настоящий S-объект в тестовой функции. Вы просто хотите обмануть компилятор, чтобы проверить, имеет ли объект S вызываемую функцию f ().
Вы можете создать собственное исключение и выбросить его так, чтобы:
#if defined(DEBUG)
#if defined(__GNUC__)
#define DEPRECATED(f, m) f __attribute__((deprecated(m)))
#elif defined(_MSC_VER)
#define DEPRECATED(f, m) __declspec(deprecated(m)) f
#else
#define DEPRECATED(f, m) f
#endif
class not_implemented : public std::logic_error {
public:
DEPRECATED(not_implemented(), "\nUnimplemented function") : logic_error("Not implemented.") { }
}
#endif // DEBUG
Не реализованные функции выглядят так:
void doComplexTask() {
throw not_implemented();
}
Вы можете искать эти невыполненные функции несколькими способами. В GCC вывод для отладочных сборок:
main.cpp: In function ‘void doComplexTask()’:
main.cpp:21:27: warning: ‘not_implemented::not_implemented()’ is deprecated:
Unimplemented function [-Wdeprecated-declarations]
throw not_implemented();
^
main.cpp:15:16: note: declared here
DEPRECATED(not_implemented(), "\nUnimplemented function") : logic_error("Not implemented.") { }
^~~~~~~~~~~~~~~
main.cpp:6:26: note: in definition of macro ‘DEPRECATED’
#define DEPRECATED(f, m) f __attribute__((deprecated(m)))
Выпуск билдов:
main.cpp: In function ‘void doComplexTask()’:
main.cpp:21:11: error: ‘not_implemented’ was not declared in this scope
throw not_implemented;
^~~~~~~~~~~~~~~
Вы можете искать исключения с grep
:
$ grep -Enr "\bthrow\s+not_implemented\b"main.cpp:21: throw not_implemented();
Преимущество использования grep
в том, что grep
не заботится о вашей конфигурации сборки и найдет все независимо. Вы также можете избавиться от устаревшего квалификатора, чтобы очистить вывод компилятора — вышеупомянутый хак генерирует много несущественного шума. В зависимости от ваших приоритетов это может быть недостатком (например, вас могут не волновать функции, специфичные для Windows, если вы в настоящее время реализуете функции, специфичные для Linux, или наоборот).
Если вы используете IDE, большинство из них позволит вам искать весь ваш проект, а некоторые даже позволят вам щелкнуть правой кнопкой мыши по символу и найти его везде, где он используется. (Но вы сказали, что не можете использовать один так в вашем случае grep
твой друг.)
Я не вижу простой способ сделать это. Наличие нескольких классов без реализации легко приведет к ситуации, когда отслеживание в команде с несколькими членами будет кошмаром.
Лично я хотел бы провести модульное тестирование каждого класса, который я пишу, и тестирование, основанное на разработке, — моя рекомендация Однако это подразумевает связывание кода каждый раз, когда вы хотите проверить статус.
Для инструментов для использования TDD обратитесь к ссылке Вот.
Другой вариант заключается в написании фрагмента кода, который может анализировать исходный код и проверять его функциональность. GCC_XML — хорошая отправная точка.