Найти нереализованные методы класса

В моем приложении я имею дело с классами большего размера (более 50 методов), каждый из которых достаточно сложен. Я не беспокоюсь о сложности, поскольку они по-прежнему прямолинейны с точки зрения изоляции частей функциональности в более мелкие методы и последующего их вызова. Вот как количество методов становится большим (многие из этих методов являются частными, в частности, изолирующими части функциональности).

Однако, когда я дошел до стадии реализации, я обнаружил, что теряю представление о том, какие методы были реализованы, а какие нет. Затем на этапе связывания я получаю ошибки для нереализованных методов. Это было бы хорошо, но между классами много взаимозависимостей, и для того, чтобы связать приложение, мне нужно было бы подготовить ВСЕ. Тем не менее, я бы предпочел пройти один класс, прежде чем переходить к следующему.

По независящим от меня причинам я не могу использовать IDE — только текстовый редактор и компилятор g++. Есть ли способ найти нереализованные методы в одном классе без полной компоновки? Прямо сейчас я буквально выполняю текстовый поиск по сигнатурам методов в файле реализации cpp для каждого из методов, но это занимает очень много времени.


person Aleks G    schedule 14.01.2013    source источник
comment
Что мешает вам просто попытаться связать и найти неопределенную ссылку на сообщение?   -  person Agentlien    schedule 14.01.2013
comment
Да, это один из способов сделать это, но я получу много таких, которые могут не относиться к рассматриваемому классу. Общий проект включает сотни классов, а сборка занимает около 30 минут.   -  person Aleks G    schedule 14.01.2013
comment
Я подозревал, что это чистое количество, которое может создать проблему. Тем не менее, вы должны иметь возможность выполнить grep для неопределенной ссылки на ClassInQuestion::, чтобы получить совпадения только для методов из этого класса. (Я сделаю ответ о том же)   -  person Agentlien    schedule 14.01.2013
comment
возможно, вы можете как-то использовать регулярное выражение и найти <methodname> в файле .h, а затем искать <myclass>::<methodname> в файле cpp? Мои навыки регулярных выражений, к сожалению, бесполезны: P   -  person Default    schedule 14.01.2013


Ответы (8)


Хотя я не вижу простого способа сделать это без фактической попытки связать, вы можете grep вывод компоновщика для «неопределенной ссылки на ClassInQuestion::», что должно дать вам только строки, связанные с этой ошибкой для методов данного класса .

Это, по крайней мере, позволяет избежать просеивания всех сообщений об ошибках всего процесса компоновки, хотя и не избавляет от необходимости проходить полную компоновку.

person Agentlien    schedule 14.01.2013
comment
Да, с точки зрения автоматизации это кажется самым простым подходом. Самая большая проблема сейчас - это 30+ минут, которые занимает сборка... - person Aleks G; 14.01.2013
comment
@AleksG Это действительно проблема, и с учетом этого я не могу найти подходящего решения, кроме как написать инструмент для сравнения файлов .h и .cpp, поиска совпадающих / отсутствующих объявлений и определений функций. - person Agentlien; 14.01.2013

Вы можете добавить заглушку для каждого метода, который вы собираетесь реализовать, и сделать:

void SomeClass::someMethod() {
    #error Not implemented
}

С gcc это выводит файл, номер строки и сообщение об ошибке для каждого из них. Таким образом, вы можете просто скомпилировать рассматриваемый модуль и выполнить grep для «Не реализовано», не требуя запуска компоновщика.

Хотя вам все равно нужно добавить эти заглушки в файлы реализации, которые могут быть частью того, что вы пытались обойти в первую очередь.

person lethal-guitar    schedule 14.01.2013
comment
Но это по-прежнему требует от меня реализации всех методов. Кроме того, мне нужно иметь возможность скомпилировать неполный класс для проверки синтаксических ошибок и т. д., прежде чем все будет готово. - person Aleks G; 14.01.2013
comment
Что ж, это правда. Хотя вы можете добавить все заглушки, а затем начать реализацию, и у вас будет простой способ увидеть, какие методы все еще являются заглушками. Но я понимаю, что это может быть не совсем то, что вы ищете. - person lethal-guitar; 14.01.2013
comment
Может быть, вы можете написать короткий add_method скрипт на Python или что-то в этом роде, который вставит вышеуказанную заглушку в конец файла cpp? Не должно быть слишком сложно, если вы не используете разные уровни вложенности пространств имен в разных файлах. - person lethal-guitar; 14.01.2013

Вот для чего нужны юнит-тесты и инструменты тестового покрытия: заранее напишите минимальные тесты для всех функций. Тесты на отсутствующие функции не будут связаны. Отчет о тестовом покрытии сообщит вам, все ли функции были посещены.

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

person Konrad Rudolph    schedule 14.01.2013
comment
Я не хочу посещать каждую функцию. Я хочу на этапе редактирования посмотреть, что я не реализовал. Чтобы запустить модульные тесты, мне нужно скомпилировать и связать - и весь смысл моего вопроса заключался в том, чтобы посмотреть, есть ли способ найти отсутствующие реализации без связывания. - person Aleks G; 14.01.2013
comment
@Алекс Ну конечно есть. В IDE. Чего вы явно не хотите. И, конечно же, есть другие инструменты, которые также предоставляют вам эту информацию. Мой ответ описывает их. - person Konrad Rudolph; 14.01.2013

В прошлом я создал исполняемый файл для каждого класса:

#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;        
}
person Peter Wood    schedule 14.01.2013
comment
Конечно, это работает только для классов с конструкторами по умолчанию (или, по крайней мере, только для тех, которые не зависят от других классов), и не проверяет, реализованы ли все функции. - person Konrad Rudolph; 14.01.2013
comment
Вы можете автоматизировать его без конструкторов по умолчанию. Вы включаете объектные файлы, необходимые для создания ссылки. Он проверяет, реализованы ли все виртуальные функции. Это наброски идеи, а не полная реализация. - person Peter Wood; 14.01.2013
comment
Было бы неплохо, если бы Klass не зависел от 50 других классов для компиляции. - person Aleks G; 14.01.2013
comment
@AleksG Честно говоря, это звучит как серьезная ошибка в дизайне класса. - person Konrad Rudolph; 14.01.2013
comment
Это идея зеленой комнаты. Узнай, как сделать это сам! - person Peter Wood; 14.01.2013

Вы можете написать небольшой скрипт, который анализирует заголовочный файл на наличие реализаций методов (регулярные выражения упростят эту задачу), а затем сканирует файл реализации на наличие тех же реализаций методов.

Например, в 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

Кто-то еще сможет рассказать вам, как автоматизировать это в процессе сборки, но это один из способов быстро проверить, какие методы еще не реализованы.

person Ephemera    schedule 14.01.2013

Ключевое слово delete С++ 11 делает свое дело

struct S{
  void f()=delete; //unimplemented
};

Если С++ 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().

person pierre    schedule 16.12.2014

Вы можете создать собственное исключение и выдать его так, чтобы:

  • Вызов нереализованной функции завершит приложение, а не оставит его в неожиданном состоянии.
  • Код все еще может быть скомпилирован, даже без реализации необходимых функций.
  • Вы можете легко найти нереализованные функции, просмотрев предупреждения компилятора (используя некоторые, возможно, неприятные приемы) или выполнив поиск в каталоге вашего проекта.
  • При желании вы можете удалить исключение из выпускных сборок, что приведет к ошибкам сборки, если есть какие-либо функции, которые пытаются генерировать исключение.
#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 — ваш друг.)

person ShadowFan-X    schedule 05.06.2018

Я не вижу простого способа сделать это. Наличие нескольких классов без реализации легко приведет к ситуации, когда отслеживание в команде с несколькими членами станет кошмаром.

Лично я хотел бы проводить модульное тестирование каждого класса, который я пишу, и я рекомендую разработку через тестирование. Однако это требует связывания кода каждый раз, когда вы хотите проверить статус. Инструменты для использования TDD см. по ссылке здесь.

Другой вариант — написать фрагмент кода, который может анализировать исходный код и проверять, какие функции должны быть реализованы. GCC_XML — хорошая отправная точка.

person Ram    schedule 14.01.2013
comment
Пожалуйста, дайте мне знать причину отрицательного голосования. Поможет мне лучше понять. Спасибо - person Ram; 14.01.2013
comment
Это означало бы фактическую реализацию каждой функции, что позволило бы связать ее, но переместило бы проблему во время выполнения. Это не всегда желательно в большом проекте. Это форсирует TDD, что может быть невозможным переходом (поскольку он предъявляет требования к проекту в целом). - person Agentlien; 14.01.2013
comment
Другой возможностью в том же духе может быть первоначальная реализация функций как макетов с помощью static_assert(false,unimplemented function) для тех, которые должны быть реализованы. - person Agentlien; 14.01.2013
comment
Я работаю над проектом, в котором 3 миллиона строк кода, и мы используем TDD. Извините, но я не согласен с вашим комментарием о большом проекте. Разработка через тестирование — один из лучших способов изолировать сложность и управлять ею. Это также помогает мне следить за тем, что осталось сделать. - person Ram; 14.01.2013
comment
Я не говорю, что его нельзя использовать в большом проекте, я думаю, что он отлично подходит для больших проектов по указанным вами причинам. Но начинать с TDD может быть управленческим решением, которое программист-одиночка не в состоянии принять. В таком случае такой общий совет не несет никакой практической пользы. - person Agentlien; 14.01.2013
comment
Проблема с этим подходом заключается в том, что он по-прежнему требует связывания. У меня возник вопрос: Есть ли способ найти нереализованные методы в одном классе без полной компоновки? - person Aleks G; 14.01.2013
comment
@AleksG Согласен. Трудно, если вообще возможно, написать фрагмент кода, который анализирует файл единицы перевода и сообщает о наличии или отсутствии реализации функции. Я могу попробовать это в свободное время, и это может не занять много времени. - person Ram; 14.01.2013
comment
@Ram Не стесняйтесь делать это, однако мой вопрос был о том, есть ли способ сделать то, что я хочу. Существует никоим образом также действительный ответ. - person Aleks G; 14.01.2013
comment
@AlexsG Возможно, ты прав. Мне было интересно, может ли GCC-XML помочь вам написать что-нибудь быстро. Просто мысль. gccxml.org/HTML/Index.html - person Ram; 14.01.2013