2008-10-14 17 views
38

Jak można przekonwertować rozsądnie dużą (> 300K), dość dojrzałą bazę kodów C do C++?Konwersja źródła C na C++

Rodzaje CI mają na myśli podział na pliki z grubsza odpowiadające modułom (tj. Mniej ziarnisty niż typowy rozkład oparty na klasach OO), z wykorzystaniem wewnętrznego powiązania w funkcji prywatnych i danych oraz zewnętrznego powiązania dla funkcji publicznych i dane. Zmienne globalne są szeroko wykorzystywane do komunikacji między modułami. Dostępny jest bardzo obszerny pakiet testów integracji, ale nie ma testów na poziomie jednostki (tj.

mam na myśli ogólną strategię:

  1. kompilować wszystkiego w C podzbioru C++ 's i zdobądź pracę.
  2. Konwertuj moduły na wielkie klasy, tak aby wszystkie odsyłacze były ograniczone zakresem nazwy klasy, ale pozostawiając wszystkie funkcje i dane jako statyczne elementy i sprawiają, że działa.
  3. Konwertuj wielkie klasy na instancje za pomocą odpowiednich konstruktorów i inicjowanych powiązań; w razie potrzeby wymieniaj dostępy do elementów statycznych na pośrednie; i zacznij działać.
  4. Teraz podejdź do projektu jako źle zinterpretowana aplikacja OO i napisz testy jednostkowe, w których zależności są możliwe do wyodrębnienia i rozłóż je na osobne klasy, w których nie są; celem byłoby przejście z jednego programu roboczego do drugiego przy każdej transformacji.

Oczywiście, to byłaby spora praca. Czy są jakieś studia przypadków/opowiadania o wojnie na temat tego rodzaju tłumaczenia? Alternatywne strategie? Inne przydatne porady?

Uwaga 1: program jest kompilatorem i prawdopodobnie miliony innych programów polegają na tym, że jego zachowanie się nie zmienia, więc przepisanie w wersji hurtowej w zasadzie nie wchodzi w grę.

Uwaga 2: źródło ma prawie 20 lat i ma prawdopodobnie 30% rezygnacji z kodu (linie zmodyfikowane + dodane/poprzednie całkowite linie) rocznie. Innymi słowy, jest to w dużym stopniu utrzymywane i rozszerzone. Tak więc jednym z celów byłoby zwiększenie możliwości utrzymania.

[Przez wzgląd na pytanie, załóżmy, że tłumaczenie na język C++ jest obowiązkowe, i że pozostawienie go w C jest nie opcja. Punktem dodania tego warunku jest wyeliminowanie odpowiedzi "zostaw to w C".]

+0

Jakie są ramy czasowe obowiązkowej migracji? – paxos1977

+0

Jak dobrze znasz bazę kodu C? Na lewą stronę? – paxos1977

+0

Tłumaczenie nie jest obowiązkowe, to tylko ze względu na pytanie (wykorzenić te odpowiedzi "nie tłumacz"). Ramy czasowe mogą wynosić 1-10 lat (jest to program długowieczny). –

Odpowiedz

15

Właśnie rozpoczęła się prawie to samo kilka miesięcy temu (na dziesięć-letniego projektu komercyjnego, pierwotnie napisany w „C++ jest tylko C z inteligentnymi struct s” filozofii), chciałbym zasugeruj użycie tej samej strategii, której użyjesz do zjedzenia słonia: weź jeden kęs naraz. :-)

W miarę możliwości podziel się na etapy, które można wykonać przy minimalnym wpływie na inne części. Budowanie systemu fasadowego, jak sugeruje Federico Ramponi, to dobry początek - gdy wszystko ma już fasadę C++ i komunikuje się za jego pośrednictwem, możesz zmienić wewnętrzne elementy modułów z pełną pewnością, że nie mogą wpływać na nic poza nimi.

Mamy już częściowy system interfejsu C++ (z powodu poprzednich mniejszych wysiłków refaktoryzacji), więc to podejście nie było trudne w naszym przypadku. Kiedy już wszystko było komunikowane jako obiekty C++ (co zajęło kilka tygodni, pracując na całkowicie oddzielnej gałęzi kodu źródłowego i integrując wszystkie zmiany z główną gałęzią, ponieważ zostały zatwierdzone), było bardzo rzadko, że nie mogliśmy skompilować całkowicie działająca wersja zanim wyjechaliśmy na dzień.

Zmiana jeszcze się nie zakończyła - dwa razy wstrzymaliśmy się na tymczasowe wydania (co kilka tygodni staramy się publikować punkt), ale jest już w drodze i żaden klient nie skarżył się na żadne problemy. Nasi pracownicy ds. Kontroli jakości znaleźli tylko jeden problem, który również pamiętam. :-)

+0

Brzmi strasznie ... Powinieneś napisać bardziej szczegółowy artykuł na temat tej procedury, założę się, że będzie dobrze czytać. –

+0

Napisałem kilka artykułów na blogu o konkretnych częściach konwersji, http://geekblog.oakcircle.com/2008/07/19/ascii-unicode-and-windows/ i http: //geekblog.oakcircle. com/2009/03/15/superbug /. Nie jestem zbyt zabawnym pisarzem, żeby wszystko było interesujące. –

5

Napisałbym klasy C++ przez interfejs C. Nie dotykanie kodu C zmniejszy szansę na zepsucie i znacznie przyspieszy proces.

Gdy masz już swój interfejs C++; to jest banalne zadanie kopiowania i wklejania kodu do twoich zajęć. Jak wspomniałeś - na tym etapie ważne jest, aby wykonać testy jednostkowe.

+3

"Interfejs C" zaczyna się i kończy na "main()".Myślę, że mogłeś zostawić kilka kroków ... :) –

11

Co o:

  1. Kompilacja wszystko w C++ C podzbioru 's i dostać tę pracę, a
  2. Wdrażanie zestaw facades pozostawiając niezmienione kod C?

Dlaczego "tłumaczenie na C++ jest obowiązkowe"? Możesz owijać kod C bez bolesnego przekształcania go w wielkie klasy i tak dalej.

+0

Jednym z punktów, dzięki którym kod staje się bardziej modułowy, konwersja do C++ i dodawanie testów jednostkowych ma na celu uczynienie go łatwiejszym w utrzymaniu. Po prostu umieszczenie fasady nad frontem po prostu się nie uda. –

+0

"Tłumaczenie na C++ obowiązkowe" polega na wyeliminowaniu odpowiedzi, które brzmią "pozostaw C bez zmian". –

+0

Również, być może kod C nie jest czystym Ansi C, ale jest jakimś starożytnym Dialektem C, który nie był czystym ANSI. :-) –

1

Jeśli masz mały projekt lub projekt akademicki (na przykład mniej niż 10 000 wierszy), przepisanie jest prawdopodobnie najlepszą opcją. Możesz to zliczyć, jak chcesz i nie zajmie to zbyt wiele czasu.

Jeśli masz aplikację w świecie rzeczywistym, proponuję skompilowanie jej jako C++ (co zwykle oznacza przede wszystkim naprawę prototypów funkcji i tym podobne), a następnie pracę nad refaktoryzacją i pakowaniem OO. Oczywiście, nie zgadzam się z filozofią, że kod musi mieć strukturę OO, aby był akceptowalnym kodem C++. Zrobiłbym konwersję "kawałek po kawałku", przepisanie i refaktoryzację według potrzeb (funkcjonalność lub włączenie testów jednostkowych).

3

Twoja lista wygląda dobrze, chyba że najpierw zaleciłbym przejrzenie zestawu testów i spróbowanie jak najmocniejszego kodu, zanim to zrobię.

+1

Zestaw testów jest dość napięty, zaufaj mi. 20 lat QA z dziesiątkami tysięcy zarejestrowanych błędów z przypadkami testowymi napisanymi przez QA ma tendencję do robienia tego. –

3

Chodźmy rzucić kolejny głupi pomysł:

  1. Kompilacja wszystko w C++ C podzbioru 's i uzyskać pracę.
  2. Zacznij od modułu, przekonwertuj go na wielką klasę, następnie w instancję i zbuduj interfejs C (identyczny z tym, z którego zacząłeś) z tej instancji. Niech pozostały kod C będzie działał z tym interfejsem C.
  3. W razie potrzeby zreaktuj, rozwijając podsystem OO z kodu C po jednym module na raz i upuszczając części interfejsu C, gdy staną się bezużyteczne.
+1

Tak, to z grubsza część 1 i 2 mojego planu, z podziałem bardziej szczegółowo. –

3

Prawdopodobnie dwie rzeczy do rozważenia oprócz jak chcesz rozpocząć się na tym, co chcesz naciskiem, i gdzie chcesz przystanku.

Podajesz, że istnieje duża liczba rezygnacji z kodu, może to być klucz do wysiłków użytkownika focus. Proponuję wybrać części kodu, w których wymagana jest duża konserwacja, starsze/stabilne części najwyraźniej działają wystarczająco dobrze, więc lepiej zostawić je w takim stanie, z wyjątkiem prawdopodobnie niektórych ubrań okiennych z fasadami itp.

Miejsce, w którym chcesz się zatrzymać, zależy od tego, jaki jest powód zamiany konwersji na C++. To nie może być samo w sobie celem. Jeśli wynika to z zależności od innych podmiotów, skoncentruj swoje wysiłki na interfejsie tego komponentu.

Oprogramowanie, nad którym pracuję, to ogromna, stara baza kodu, która została "przekonwertowana" z C na C++ lat temu. Myślę, że to dlatego, że GUI zostało przekonwertowane na Qt. Nawet teraz nadal wygląda jak program klasy C z klasami. Przełamując zależności powodowanych przez członków danych publicznych oraz refactoring ogromne zajęcia z proceduralnych metod potworów na mniejsze metod i klas nigdy naprawdę zdjęty, myślę, że z następujących powodów:

  1. Nie ma potrzeby, aby zmienić kod działa i nie trzeba go ulepszać. Wprowadza to nowe błędy bez dodawania funkcjonalności, a użytkownicy końcowi tego nie doceniają;
  2. To bardzo, bardzo trudne do zrobienia refactor niezawodnie. Wiele fragmentów kodu jest tak dużych i równie ważnych, że ludzie nie mają odwagi go dotknąć. Mamy dość obszerny zestaw testów funkcjonalnych, ale trudno jest uzyskać wystarczającą ilość informacji na temat zasięgu kodu. W związku z tym trudno jest ustalić, czy istnieją już wystarczające testy do wykrycia problemów podczas refaktoryzacji;
  3. ROI trudno jest ustalić. Użytkownik końcowy nie będzie odnosił korzyści z refaktoryzacji, więc musi być zredukowany koszt utrzymania, który początkowo wzrośnie, ponieważ przez refaktoryzację wprowadzasz nowe błędy w dojrzałym, tj. Dość wolnym od błędów kodzie. A sam refaktoryzacja będzie kosztowna ...

NB. Przypuszczam, że znasz książkę "Working effective with Legacy code"?

+1

Tak, mam książkę. Niestety prawie w całości dotyczy tylko kodu testowalnego przez jednostkę. Główna sugestia - ledwie więcej niż akapit, który sobie przypominam - dla osób używających kodu spoza zakresu OO była wersja OO. –

+1

Informacja o tym, że w większości wciąż wyglądam jak C, mogę z tym żyć. Jak już powiedziałem, istnieje znaczna liczba rezygnacji, więc możliwość korzystania z C++ w przypadku przepisywanych utworów w dalszym ciągu będzie zwycięstwem pod względem modularności. –

+0

Nie ma naprawdę żadnych "stabilnych" części, per se, poza menadżerem pamięci. Podstawowymi celami byłoby zwiększenie nowego poziomu abstrakcji kodu źródłowego dzięki starannemu użyciu szablonów, klas i zmniejszeniu wzajemnych zależności, w szczególności powodowanych przez zmienne globalne. –

1

Oto co zrobię:

  • Ponieważ kod ma 20 lat, złom w dół parser/analizator składni i zastąpienie go jednym z nowszych lex/yacc/żubrów (lub coś podobnego) etc oparty kod C++, o wiele łatwiejszy do utrzymania i łatwiejszy do zrozumienia. Szybciej się też rozwija, jeśli masz pod ręką BNF.
  • Po doposażeniu starego kodu zacznij pakować moduły w klasy. Zamień zmienne globalne/współdzielone na interfejsy.
  • Teraz masz kompilator w C++ (choć nie do końca).
  • Narysuj diagram klas wszystkich klas w swoim systemie i zobacz, jak się komunikują.
  • Narysuj inną przy użyciu tych samych klas i zobacz, jak powinny się komunikować.
  • Zmień kod, aby przekształcić pierwszy diagram na drugi. (może to być kłopotliwe i trudne)
  • Pamiętaj, aby używać kodu C++ do dodawania wszystkich nowych kodów.
  • Jeśli masz trochę czasu, spróbuj zastąpić struktury danych jeden po drugim, aby użyć bardziej znormalizowanego STL lub zwiększenia.
+1

Nie sądzę, że docenisz wszystkie subtelności kompilatorów. Kompilatory komercyjne używają ręcznie pisanych lexerów i analizatorów składni z wielu powodów, a wydajność jest tylko jedna. Po drugie, nie uzależniaj się zbytnio od zajęć. Funkcje wielokrotnego wysyłania w funkcji CLOS byłyby częściej przydatne niż metody wirtualne. –

+0

Na przykład, jak zmienić klasę instancji w locie? Jak tworzysz nowe klasy w czasie wykonywania? Kończysz dodawanie poziomów pośrednictwa i tracisz dużo zwykłej korzyści z OO. W rzeczywistości dopasowywanie wzorców, a nie tylko dopasowywanie typów, w wielu wysyłkach byłoby jeszcze lepsze. –

+0

Re parser: język nie jest LL (1), ani LALR (1), jest wrażliwy na kontekst w sposób, w jaki można rozwiązać ad-hoc semantyczne i składniowe predykaty. Jest to cena elastycznego przedłużania języka na przestrzeni lat. –

7

Twoja aplikacja ma wielu ludzi, którzy nad nią pracują, i potrzebę nie bycia złamanym. Jeśli poważnie myślisz o konwersji na dużą skalę do stylu OO, potrzebujesz ogromnych narzędzi do transformacji, aby zautomatyzować pracę.

Podstawowa idea jest do wyznaczenia grupy danych, klas, a następnie uzyskać narzędzie do byłaby kod, aby przenieść te dane do klas, funkcji Move On tylko tych danych do tych klas, i przeglądu wszystkich dostępów do dane do wywołań na zajęciach.

Możesz wykonać zautomatyzowaną analizę wstępną, aby utworzyć klastry statystyczne, aby uzyskać pewne pomysły, , ale nadal potrzebujesz inżyniera świadomego, aby zdecydować, które elementy danych należy pogrupować.

Narzędzie, które może wykonać to zadanie, jest naszym DMS Software Reengineering Toolkit. DMS posiada silne parsery C do odczytu kodu, przechwytuje kod C jako drzewa składniowe abstrakcyjnej kompilacji (i w przeciwieństwie do konwencjonalnego kompilatora) potrafi obliczyć analizy przepływu w całym 300K SLOC. DMS ma przedni koniec C++, który może być używany jako koniec "z powrotem"; zapisuje transformacje, które odwzorowują składnię C na składnię C++.

Zasadnicze zadanie przebudowy C++ na dużym systemie awioniki daje pewną wiedzę na temat tego, jak używać DMS do tego rodzaju działalności. Patrz dokumenty techniczne w www.semdesigns.com/Products/DMS/DMSToolkit.html, specjalnie Re-engineering modeli C++ składników przez automatyczny program transformacji

Proces ten nie jest dla osób o słabym sercu. Jednak każdy, kto rozważałby ręczne refaktoryzacje dużego zastosowania , już nie boi się ciężkiej pracy.

Tak, jestem związany z firmą, będąc jej głównym architektem.

+2

dobry wpis, ale możesz dodać, że jesteś powiązany z wymienionym produktem i firmą, w przeciwnym razie ludzie zaczną to wskazywać, wywołując ogłoszenie o ukrytej reklamie ;-) – none

+0

To brzmi jak droga ... i posiadający kod awioniki oznacza, że ​​musi być cholernie pewny, że działa. –

+0

@ none - Znam Ira z comp.compilers. Ta konwersja prawdopodobnie nie dotyczy nas, nie tylko ze względu na koszty/ryzyko, ale dlatego, że badamy inne możliwości. Jednak odpowiedź jest przydatna dla innych osób z podobnymi problemami ... –

4

GCC jest obecnie w środkowej fazie przejścia do C++ z C. Zaczęło się od przeniesienia wszystkiego do wspólnego podzbioru C i C++. Gdy to zrobili, dodali ostrzeżenia do GCC na wszystko, co znaleźli, znalezione pod -Wc++-compat. To powinno cię zabrać na pierwszą część twojej podróży.

Dla tych ostatnich części, gdy faktycznie wszystko będzie kompilowane przy użyciu kompilatora C++, skupiłbym się na wymianie rzeczy, które mają idiomatyczne odpowiedniki C++. Na przykład, jeśli korzystasz z list, map, zestawów, wektorów bitowych, tablic asynchronicznych itp., Które są zdefiniowane przy użyciu makr C, najprawdopodobniej uzyskasz dużo, przenosząc je do C++. Podobnie z OO, prawdopodobnie znajdziesz zalety tam, gdzie już używasz idiomu C OO (jak dziedziczenie struktury), i gdzie C++ będzie zapewniać większą klarowność i lepsze sprawdzanie kodu.

2

Wspomniałeś, że twoje narzędzie jest kompilatorem i że: "W rzeczywistości dopasowywanie wzorców, a nie tylko dopasowywanie typów, w wielu wysyłkach byłoby jeszcze lepsze". Można uzyskać maketea. Zapewnia dopasowanie wzorców do AST, a także definicję AST z gramatyki abstrakcyjnej, a odwiedzający, tranformatorzy itd.