Jak duże bazy kodu inne niż OO są zarządzane?

27

Zawsze widzę, że abstrakcja jest bardzo przydatną funkcją OO do zarządzania bazą kodu. Ale w jaki sposób zarządzane są duże bazy kodu inne niż OO? A może ostatecznie stają się „ Wielką Kulą Błota ”?

Aktualizacja:
Wydawało się, że wszyscy myślą, że „abstrakcja” to po prostu modularyzacja lub ukrywanie danych. Ale IMHO oznacza także użycie „klas abstrakcyjnych” lub „interfejsów”, które są niezbędne do wstrzykiwania zależności, a tym samym do testowania. Jak zarządzają tym bazy kodu inne niż OO? Ponadto, oprócz abstrakcji, enkapsulacja bardzo pomaga w zarządzaniu dużymi bazami kodu, ponieważ definiuje i ogranicza relacje między danymi a funkcjami.

Dzięki C bardzo możliwe jest pisanie kodu pseudo-OO. Nie wiem wiele o innych językach innych niż OO. Czy jest to zatem sposób zarządzania dużymi bazami kodu C?

Gulshan
źródło
6
W sposób agnostyczny z języka opisz obiekt. Co to jest, jak jest modyfikowany, co powinien odziedziczyć i co powinien zapewnić? Jądro Linux jest pełne przydzielonych struktur z mnóstwem pomocników i wskaźników funkcji, ale prawdopodobnie nie spełniałoby to definicji zorientowanej obiektowo dla większości. Jest to jednak jeden z najlepszych przykładów bardzo dobrze utrzymanej bazy kodu. Czemu? Ponieważ każdy opiekun podsystemu wie, co leży w zakresie jego odpowiedzialności.
Tim Post
W sposób niezależny od języka opisz, w jaki sposób widzisz zarządzane bazy kodu i co OO ma z tym wspólnego.
David Thornley
@Tim Post Jestem zainteresowany zarządzaniem kodem źródłowym jądra Linux. Czy mógłbyś bardziej opisać system? Być może jako odpowiedź z przykładem?
Gulshan,
7
W dawnych czasach do testowania jednostkowego używaliśmy osobnego linkowania do prób i kodów pośredniczących. Wstrzykiwanie zależności jest tylko jedną z wielu technik. Kompilacja warunkowa to kolejna.
Macneil,
Myślę, że rozciąganie się na dużych bazach kodu (OO lub w inny sposób) to „zarządzany”. Dobrze byłoby mieć lepszą definicję centralnego terminu w swoim pytaniu.
tottinge

Odpowiedzi:

43

Wydaje ci się, że OOP jest jedynym sposobem na osiągnięcie abstrakcji.

Chociaż OOP z pewnością jest w tym bardzo dobry, nie jest to jednak jedyny sposób. Zarządzanie dużymi projektami może być również utrzymywane przez bezkompromisową modularyzację (wystarczy spojrzeć na Perla lub Pythona, z których oba są doskonałe, podobnie jak języki funkcjonalne, takie jak ML i Haskell), oraz za pomocą mechanizmów takich jak szablony (w C ++).

Konrad Rudolph
źródło
27
+1 Ponadto można napisać „Big Ball of Mud” za pomocą OOP, jeśli nie wiesz, co robisz.
Larry Coleman,
Co z bazami kodu C?
Gulshan
6
@Gulshan: Wiele dużych baz kodu C ma postać OOP. To, że C nie ma klas, nie oznacza, że ​​OOP nie można osiągnąć przy odrobinie wysiłku. Ponadto C umożliwia dobrą modularyzację przy użyciu nagłówków i idiomu PIMPL. Nie tak wygodne i wydajne jak moduły we współczesnych językach, ale znowu wystarczająco dobre.
Konrad Rudolph
9
C umożliwia modularyzację na poziomie pliku. Interfejs znajduje się w pliku .h, funkcje publicznie dostępne w pliku .c, a zmienne i funkcje prywatne są staticdołączane do modyfikatora dostępu.
David Thornley
1
@Konrad: chociaż zgadzam się, że OOP nie jest jedynym sposobem, aby to zrobić, uważam, że OP prawdopodobnie miał na myśli wyłącznie C, który nie jest językiem funkcjonalnym ani dynamicznym. Wątpię więc, by wspominanie o Perlu i Haskellu miało dla niego jakikolwiek sens. Uważam, że twój komentarz jest bardziej odpowiedni i przydatny dla OP ( nie oznacza, że ​​OOP nie można osiągnąć przy odrobinie wysiłku ); możesz rozważyć dodanie go jako osobnej odpowiedzi z dodatkowymi szczegółami, być może obsługiwanej przez fragment kodu lub kilka linków. Przynajmniej wygrałbym mój głos, a być może OP. :)
Groo
11

Moduły, funkcje (zewnętrzne / wewnętrzne), podprogramy ...

jak powiedział Konrad, OOP nie jest jedynym sposobem zarządzania dużymi bazami kodu. W rzeczywistości napisano przed nim dość dużo oprogramowania (przed C ++ *).

Wieża
źródło
* I tak, wiem, że C ++ nie jest jedyną obsługą OOP, ale jakoś wtedy to podejście zaczęło przyjmować bezwładność.
Rook
8

Zasada modułowości nie ogranicza się do języków obiektowych.

Paul Nathan
źródło
6

Realistycznie albo rzadkie zmiany (myślę o obliczeniach emerytalnych w systemie zabezpieczenia społecznego) i / lub głęboko zakorzeniona wiedza, ponieważ ludzie utrzymujący taki system jak to robią od jakiegoś czasu (cyniczne zajęcie to bezpieczeństwo pracy).

Lepsze rozwiązania to powtarzalna walidacja, przez którą rozumiem test automatyczny (np. Testowanie jednostkowe) i testowanie na ludziach, które wykonują zabronione kroki (np. Testowanie regresyjne) „w przeciwieństwie do klikania i sprawdzania, co się psuje”.

Aby rozpocząć przejście do pewnego rodzaju zautomatyzowanego testowania z istniejącą bazą kodów, polecam przeczytanie „Efektywnej pracy Michaela Feathera ze starszym kodem” , która szczegółowo opisuje podejście do wprowadzania istniejących baz kodu, aż do pewnego rodzaju powtarzalnej struktury testowej OO, czy nie. Prowadzi to do pomysłów, na które inni odpowiedzieli, takich jak modularyzacja, ale książka opisuje właściwe podejście do tego, ale nie psuje rzeczy.

orangepips
źródło
+1 za książkę Michaela Feather. Kiedy czujesz się przygnębiony dużą brzydką bazą kodu, (ponownie) przeczytaj ją :)
Matthieu
5

Chociaż wstrzykiwanie zależności oparte na interfejsach lub klasach abstrakcyjnych jest bardzo dobrym sposobem przeprowadzania testów, nie jest konieczne. Nie zapominaj, że prawie każdy język ma wskaźnik funkcji lub eval, który może zrobić wszystko, co możesz zrobić z interfejsem lub klasą abstrakcyjną (problem polega na tym, że mogą zrobić więcej , w tym wiele złych rzeczy, i że nie „ same w sobie dostarczają metadane). Taki program może faktycznie osiągnąć wstrzyknięcie zależności za pomocą tych mechanizmów.

Bardzo rygorystycznie podchodzę do metadanych. W językach OO relacje między bitami kodu są definiowane (do pewnego stopnia) przez strukturę klas, w sposób wystarczająco znormalizowany, aby mieć takie funkcje jak API refleksji. W językach proceduralnych pomocne może być ich samodzielne wymyślenie.

Odkryłem również, że generowanie kodu jest znacznie bardziej pomocne w języku proceduralnym (w porównaniu do języka zorientowanego obiektowo). To gwarantuje, że metadane są zsynchronizowane z kodem (ponieważ są używane do jego generowania) i daje coś w rodzaju punktów odcięcia programowania zorientowanego na aspekty - miejsce, w którym można wstrzyknąć kod, gdy jest potrzebny. Czasami jest to jedyny sposób na programowanie w trybie DRY w takim środowisku, które mogę zrozumieć.

psr
źródło
3

W rzeczywistości, jak niedawno odkryłeś , funkcje pierwszego rzędu są wszystkim, czego potrzebujesz do odwrócenia zależności.

C obsługuje funkcje pierwszego rzędu, a nawet do pewnego stopnia zamknięcia . Makra C są potężną funkcją do programowania ogólnego, jeśli są obsługiwane z należytą ostrożnością.

Wszystko tam jest. SGLIB jest dość dobrym przykładem na to, jak C może być użyte do pisania kodu wielokrotnego użytku. I wierzę, że jest o wiele więcej.

back2dos
źródło
2

Nawet bez abstrakcji większość programów jest podzielona na jakieś sekcje. Te sekcje zwykle dotyczą konkretnych zadań lub czynności i pracujesz nad nimi w taki sam sposób, jak pracowałbyś nad najbardziej szczegółowymi bitami abstrakcyjnych programów.

W małych i średnich projektach czasami jest to łatwiejsze dzięki purystycznej implementacji OO.

Rachunek
źródło
2

Abstrakcja, klasy abstrakcyjne, wstrzykiwanie zależności, enkapsulacja, interfejsy itp. Nie są jedynym sposobem kontrolowania dużych baz kodu; jest to sprawiedliwy i obiektowy sposób.

Głównym sekretem jest unikanie myślenia OOP podczas kodowania non-OOP.

Modułowość jest kluczem w językach innych niż OO. W C osiąga się to tak, jak wspomniał David Thornley w komentarzu:

Interfejs znajduje się w pliku .h, funkcje publicznie dostępne w pliku .c, a do zmiennych prywatnych i funkcji dołączany jest statyczny modyfikator dostępu.

mouviciel
źródło
1

Jednym ze sposobów zarządzania kodem jest rozpakowanie go na następujące typy kodu, zgodnie z architekturą MVC (model-view-controller).

  • Procedury obsługi danych wejściowych - ten kod dotyczy urządzeń wejściowych, takich jak mysz, klawiatura, port sieciowy lub abstrakcje wyższego poziomu, takie jak zdarzenia systemowe.
  • Procedury obsługi wyjść - ten kod dotyczy używania danych do manipulowania urządzeniami zewnętrznymi, takimi jak monitory, światła, porty sieciowe itp.
  • Modele - ten kod dotyczy deklarowania struktury trwałych danych, reguł sprawdzania poprawności trwałych danych i zapisywania trwałych danych na dysku (lub innym trwałym urządzeniu danych).
  • Widoki - Ten kod zajmuje się formatowaniem danych w celu spełnienia wymagań różnych metod przeglądania, takich jak przeglądarki internetowe (HTML / CSS), GUI, wiersz poleceń, formaty danych protokołu komunikacyjnego (np. JSON, XML, ASN.1 itp.).
  • Algorytmy - ten kod wielokrotnie przekształca zestaw danych wejściowych w zestaw danych wyjściowych tak szybko, jak to możliwe.
  • Kontrolery - ten kod pobiera dane wejściowe za pośrednictwem procedur obsługi danych wejściowych, analizuje dane wejściowe za pomocą algorytmów, a następnie przekształca dane za pomocą innych algorytmów, opcjonalnie łącząc dane wejściowe z trwałymi danymi lub po prostu transformując dane wejściowe, a następnie opcjonalnie zapisując przekształcone dane w pamięci trwałej za pomocą modelu oprogramowanie i opcjonalnie przekształcanie danych za pomocą oprogramowania do wyświetlania w celu renderowania na urządzeniu wyjściowym.

Ta metoda organizacji kodu działa dobrze w przypadku oprogramowania napisanego w dowolnym języku OO lub innym niż OO, ponieważ wspólne wzorce projektowe są często wspólne dla każdego z obszarów. Ponadto tego rodzaju granice kodu są często najbardziej luźno powiązane, z wyjątkiem algorytmów, ponieważ łączą ze sobą formaty danych z danych wejściowych do modelu, a następnie z danymi wyjściowymi.

Ewolucje systemu często przybierają formę obsługi przez oprogramowanie większej liczby rodzajów danych wejściowych lub większej liczby rodzajów danych wyjściowych, ale modele i widoki są takie same, a kontrolery zachowują się bardzo podobnie. Lub system może z czasem potrzebować obsługiwać coraz więcej różnych rodzajów danych wyjściowych, nawet jeśli dane wejściowe, modele, algorytmy są takie same, a kontrolery i widoki są podobne. Lub system może zostać rozszerzony o nowe modele i algorytmy dla tego samego zestawu danych wejściowych, podobnych danych wyjściowych i podobnych widoków.

Jednym ze sposobów, w jaki programowanie OO utrudnia organizację kodu, jest to, że niektóre klasy są głęboko powiązane z trwałymi strukturami danych, a inne nie. Jeśli trwałe struktury danych są ściśle związane z takimi rzeczami, jak kaskadowe relacje 1: N lub relacje m: n, bardzo trudno jest określić granice klas, dopóki nie zakodujesz znaczącej i znaczącej części systemu, zanim nie będziesz wiedział, że masz rację . Każda klasa powiązana z trwałymi strukturami danych będzie trudna do ewolucji, gdy zmieni się schemat trwałych danych. Klasy, które obsługują algorytmy, formatowanie i analizowanie, są mniej podatne na zmiany w schemacie trwałych struktur danych. Użycie organizacji kodu typu MVC lepiej izoluje najbardziej niechlujne zmiany kodu w kodzie modelu.

Jay Godse
źródło
0

Podczas pracy w językach, w których brakuje wbudowanej struktury i funkcji organizacyjnych (np. Jeśli nie ma przestrzeni nazw, pakietów, zestawów itp.) Lub gdy są one niewystarczające, aby kontrolować bazę kodu o takiej wielkości, naturalną reakcją jest rozwinięcie nasze własne strategie organizowania kodu.

Ta strategia organizacji prawdopodobnie obejmuje standardy dotyczące tego, gdzie należy przechowywać różne pliki, rzeczy, które muszą się zdarzyć przed / po pewnych rodzajach operacji, konwencje nazewnictwa i inne standardy kodowania, a także wiele „w ten sposób jest skonfigurowany - nie zadzieraj z tym! ” wpisz komentarze - które są ważne, o ile wyjaśniają dlaczego!

Ponieważ strategia najprawdopodobniej ostatecznie zostanie dostosowana do konkretnych potrzeb projektu (ludzi, technologii, środowiska itp.), Trudno jest stworzyć jedno uniwersalne rozwiązanie do zarządzania dużymi bazami kodu.

Dlatego uważam, że najlepszą radą jest przyjęcie strategii specyficznej dla projektu i uczynienie z niej zarządzania kluczowym priorytetem: udokumentuj strukturę, dlaczego tak jest, procesy wprowadzania zmian, poddaj ją audytowi, aby upewnić się, że jest przestrzegana, i co najważniejsze: zmień to, kiedy trzeba to zmienić.

Znamy się głównie na lekcjach i metodach refaktoryzacji, ale przy dużej bazie kodu w takim języku sama strategia organizacyjna (wraz z dokumentacją) wymaga refaktoryzacji w razie potrzeby.

Rozumowanie jest takie samo, jak w przypadku refaktoryzacji: rozwiniesz mentalny blok w kierunku pracy nad małymi częściami systemu, jeśli uważasz, że ogólna organizacja tego bałaganu, i ostatecznie pozwoli to się pogorszyć (przynajmniej takie jest moje zdanie to).

Zastrzeżenia są również takie same: użyj testu regresji, upewnij się, że możesz łatwo cofnąć, jeśli refaktoryzacja się nie powiedzie, i zaprojektuj, aby ułatwić refaktoryzację w pierwszej kolejności (lub po prostu tego nie zrobisz!).

Zgadzam się, że jest to o wiele trudniejsze niż refaktoryzacja kodu bezpośredniego i trudniej jest zweryfikować / ukryć czas przed menedżerami / klientami, którzy mogą nie rozumieć, dlaczego należy to zrobić, ale są to również rodzaje projektów najbardziej podatnych na gnicie oprogramowania spowodowane nieelastycznymi konstrukcjami najwyższego poziomu ...

AndyHasIt
źródło
0

Jeśli pytasz o zarządzanie dużą bazą kodu, pytasz, jak utrzymać dobrą strukturę bazy kodu na stosunkowo zgrubnym poziomie (biblioteki / moduły / budowanie podsystemów / korzystanie z przestrzeni nazw / posiadanie odpowiednich dokumentów we właściwych miejscach itp.). Zasady OO, zwłaszcza „klasy abstrakcyjne” lub „interfejsy”, to zasady utrzymywania kodu w czystości wewnątrz, na bardzo szczegółowym poziomie. Zatem techniki utrzymywania zarządzania dużą bazą kodu nie różnią się w przypadku kodu OO ani kodu innego niż OO.

Doktor Brown
źródło
0

Sposób obsługi polega na tym, że poznajesz granice używanych elementów. Na przykład następujące elementy w C ++ mają wyraźną granicę i wszelkie zależności poza nią muszą być dokładnie przemyślane:

  1. darmowa funkcja
  2. funkcja członka
  3. klasa
  4. obiekt
  5. berło
  6. wyrażenie
  7. wywoływanie / tworzenie obiektów przez konstruktora
  8. wywołanie funkcji
  9. typ parametru szablonu

Łącząc te elementy i rozpoznając ich granice, możesz stworzyć prawie dowolny styl programowania w c ++.

Przykładem takiej funkcji może być rozpoznanie, że źle jest wywoływać inne funkcje z funkcji, ponieważ powoduje to zależność, zamiast tego należy wywoływać tylko funkcje składowe parametrów oryginalnej funkcji.

tp1
źródło
-1

Największym wyzwaniem technicznym jest problem przestrzeni nazw. Aby obejść ten problem, można użyć częściowego łączenia. Lepszym podejściem jest projektowanie przy użyciu standardów kodowania. W przeciwnym razie wszystkie symbole staną się bałaganem.

Jonathan Cline IEEE
źródło
-2

Emacs jest tego dobrym przykładem:

Architektura Emacsa

Komponenty Emacsa

Testy Emacs Lisp używają skip-unlessi let-binddo wykrywania funkcji i urządzeń testowych:

Czasami przeprowadzanie testu nie ma sensu z powodu brakujących warunków wstępnych. Wymagana funkcja Emacsa może nie zostać wkompilowana, funkcja do przetestowania może wywołać zewnętrzny plik binarny, który może nie być dostępny na maszynie testowej, nazwij ją. W takim przypadku makra skip-unlessmożna użyć do pominięcia testu:

 (ert-deftest test-dbus ()
   "A test that checks D-BUS functionality."
   (skip-unless (featurep 'dbusbind))
   ...)

Wynik uruchomienia testu nie powinien zależeć od aktualnego stanu środowiska, a każdy test powinien pozostawić swoje środowisko w tym samym stanie, w jakim je znalazł. W szczególności test nie powinien zależeć od żadnych zmiennych dostosowywania Emacsa lub zaczepów, oraz jeśli musi wprowadzić zmiany w stanie Emacsa lub stan zewnętrzny w stosunku do Emacsa (taki jak system plików), powinien cofnąć te zmiany przed powrotem, niezależnie od tego, czy przeszedł, czy nie.

Testy nie powinny zależeć od środowiska, ponieważ wszelkie takie zależności mogą powodować, że test jest kruchy lub prowadzić do awarii, które występują tylko w pewnych okolicznościach i są trudne do odtworzenia. Oczywiście testowany kod może mieć ustawienia wpływające na jego zachowanie. W takim przypadku najlepiej wykonać test let-bindwszystkich takich zmiennych ustawień, aby skonfigurować określoną konfigurację na czas trwania testu. Test może również skonfigurować wiele różnych konfiguracji i uruchomić testowany kod dla każdej z nich.

Podobnie jak SQLite. Oto jego projekt:

  1. sqlite3_open () → Otwórz połączenie z nową lub istniejącą bazą danych SQLite. Konstruktor dla sqlite3.

  2. sqlite3 → Obiekt połączenia z bazą danych. Utworzony przez sqlite3_open () i zniszczony przez sqlite3_close ().

  3. sqlite3_stmt → Przygotowany obiekt instrukcji. Utworzony przez sqlite3_prepare () i zniszczony przez sqlite3_finalize ().

  4. sqlite3_prepare () → Skompiluj tekst SQL w bajtowy kod, który wykona kwerendę lub aktualizację bazy danych. Konstruktor dla sqlite3_stmt.

  5. sqlite3_bind () → Przechowuj dane aplikacji w parametrach oryginalnego SQL.

  6. sqlite3_step () → Przejdź do sqlite3_stmt do następnego wiersza wyników lub do zakończenia.

  7. sqlite3_column () → Wartości kolumn w bieżącym wierszu wyników dla sqlite3_stmt.

  8. sqlite3_finalize () → Destructor dla sqlite3_stmt.

  9. sqlite3_exec () → Funkcja otoki, która wykonuje sqlite3_prepare (), sqlite3_step (), sqlite3_column () i sqlite3_finalize () dla ciągu jednej lub więcej instrukcji SQL.

  10. sqlite3_close () → Destructor dla sqlite3.

architektura sqlite3

Komponenty Tokenizer, Parser i Code Generator służą do przetwarzania instrukcji SQL i konwertowania ich na programy wykonywalne w języku maszyny wirtualnej lub w kodzie bajtowym. Z grubsza mówiąc, te trzy górne warstwy implementują sqlite3_prepare_v2 () . Kod bajtowy wygenerowany przez trzy górne warstwy jest przygotowaną instrukcją. Moduł maszyny wirtualnej jest odpowiedzialny za uruchomienie kodu bajtu instrukcji SQL. Moduł B-Tree organizuje plik bazy danych w wiele magazynów kluczy / wartości z uporządkowanymi kluczami i wydajnością logarytmiczną. Moduł Pager odpowiada za ładowanie stron pliku bazy danych do pamięci, wdrażanie i kontrolowanie transakcji oraz tworzenie i utrzymywanie plików dziennika, które zapobiegają uszkodzeniu bazy danych po awarii lub awarii zasilania. Interfejs systemu operacyjnego jest cienką abstrakcją, która zapewnia wspólny zestaw procedur dostosowywania SQLite do działania w różnych systemach operacyjnych. Z grubsza mówiąc, dolne cztery warstwy implementują sqlite3_step () .

Tabela wirtualna sqlite3

Tabela wirtualna to obiekt zarejestrowany przy otwartym połączeniu z bazą danych SQLite. Z perspektywy instrukcji SQL wirtualny obiekt tabeli wygląda jak każda inna tabela lub widok. Ale za kulisami zapytania i aktualizacje wirtualnej tabeli wywołują metody wywołania zwrotnego wirtualnego obiektu tabeli zamiast odczytu i zapisu w pliku bazy danych.

Tabela wirtualna może reprezentować struktury danych w pamięci. Lub może reprezentować widok danych na dysku, który nie jest w formacie SQLite. Lub aplikacja może obliczyć zawartość wirtualnej tabeli na żądanie.

Oto niektóre istniejące i postulowane zastosowania tabel wirtualnych:

Interfejs wyszukiwania pełnotekstowego
Wskaźniki przestrzenne z wykorzystaniem R-drzew
Sprawdź zawartość dysku w pliku bazy danych SQLite (wirtualna tabela dbstat)
Odczyt i / lub zapis zawartości pliku wartości rozdzielanych przecinkami (CSV)
Uzyskaj dostęp do systemu plików komputera hosta, tak jakby był tabelą bazy danych
Umożliwianie manipulacji SQL danymi w pakietach statystycznych takich jak R.

SQLite wykorzystuje różnorodne techniki testowania, w tym:

Trzy niezależnie opracowane uprzęże testowe
100% zasięgu testu w oddziale w konfiguracji po wdrożeniu
Miliony przypadków testowych
Testy braku pamięci
Testy błędów we / wy
Testy zderzeniowe i utraty mocy
Testy Fuzz
Testy wartości granicznych
Wyłączone testy optymalizacyjne
Testy regresji
Zniekształcone testy bazy danych
Szerokie zastosowanie kontroli assert () i kontroli w czasie wykonywania
Analiza Valgrinda
Niezdefiniowane kontrole zachowania
Listy kontrolne

Referencje

Paul Sweatte
źródło