Gdzie należy umieścić stałe i dlaczego?


34

W naszych przeważnie dużych aplikacjach zwykle mamy tylko kilka lokalizacji dla „stałych”:

  • Jedna klasa dla graficznego interfejsu użytkownika i elementów wewnętrznych (tytuły stron tabulatora, tytuły pól grupy, współczynniki obliczeniowe, wyliczenia)
  • Jedna klasa dla tabel i kolumn bazy danych (ta część jest generowana kodem) oraz czytelne nazwy dla nich (przypisane ręcznie)
  • Jedna klasa dla komunikatów aplikacji (logowanie, skrzynki komunikatów itp.)

Stałe są zwykle podzielone na różne struktury w tych klasach. W naszych aplikacjach C ++ stałe są definiowane tylko w pliku .h, a wartości są przypisywane w pliku .cpp.

Jedną z zalet jest to, że wszystkie ciągi znaków itp. Znajdują się w jednym centralnym miejscu i wszyscy wiedzą, gdzie je znaleźć, gdy trzeba coś zmienić.

Szczególnie podoba się to menedżerom projektów, gdy ludzie przychodzą i odchodzą. W ten sposób każdy może zmienić takie trywialne rzeczy bez konieczności zagłębiania się w strukturę aplikacji.

Ponadto możesz łatwo zmienić tytuł podobnych pól grup / stron kart itp. Jednocześnie. Innym aspektem jest to, że możesz po prostu wydrukować tę klasę i przekazać ją nieprogramiście, który może sprawdzić, czy napisy są intuicyjne i czy wiadomości dla użytkownika są zbyt szczegółowe lub zbyt mylące itp.

Widzę jednak pewne wady:

  • Każda klasa jest ściśle powiązana z klasami stałych
  • Dodanie / usunięcie / zmiana nazwy / przeniesienie stałej wymaga ponownej kompilacji co najmniej 90% aplikacji (uwaga: zmiana wartości nie, przynajmniej dla C ++). W jednym z naszych projektów C ++ z 1500 klasami oznacza to około 7 minut czasu kompilacji (przy użyciu prekompilowanych nagłówków; bez nich to około 50 minut) plus około 10 minut łączenia z niektórymi bibliotekami statycznymi.
  • Zbudowanie wersji zoptymalizowanej pod kątem prędkości za pomocą kompilatora Visual Studio zajmuje do 3 godzin. Nie wiem, czy źródłem jest ogromna liczba relacji klasowych, ale równie dobrze może być.
  • Zostajesz wprowadzony do tymczasowo zakodowanych ciągów znaków bezpośrednio w kodzie, ponieważ chcesz przetestować coś bardzo szybko i nie chcesz czekać 15 minut tylko na ten test (i prawdopodobnie każdy kolejny). Wszyscy wiedzą, co dzieje się z myślami „Naprawię to później”.
  • Ponowne użycie klasy w innym projekcie nie zawsze jest takie łatwe (głównie ze względu na inne ciasne połączenia, ale obsługa stałych nie ułatwia).

Gdzie będziesz przechowywać takie stałe? Jakie argumenty przedstawiłbyś, aby przekonać swojego kierownika projektu, że istnieją lepsze koncepcje, które są również zgodne z wymienionymi wyżej zaletami?

Udziel odpowiedzi niezależnej lub specyficznej dla C ++.

PS: Wiem, że to pytanie jest subiektywne, ale szczerze mówiąc, nie znam lepszego miejsca niż ta strona do tego rodzaju pytań.

Zaktualizuj ten projekt

Mam wiadomości na temat czasu kompilacji:
śledząc posty Caleba i gbjbaanba, podzieliłem plik stałych na kilka innych plików, gdy miałem czas. W końcu podzieliłem swój projekt na kilka bibliotek, co było teraz znacznie łatwiejsze. Kompilacja tego w trybie wydania pokazała, że ​​automatycznie wygenerowany plik zawierający definicje bazy danych (tabela, nazwy kolumn i więcej - ponad 8000 symboli) i gromadzący pewne skróty spowodował ogromne czasy kompilacji w trybie wydania.

Dezaktywacja optymalizatora MSVC dla biblioteki, która zawiera stałe DB, pozwoliła nam teraz skrócić całkowity czas kompilacji twojego projektu (kilku aplikacji) w trybie wydania z maksymalnie 8 godzin do mniej niż jednej godziny!

Musimy jeszcze dowiedzieć się, dlaczego MSVC tak trudno jest zoptymalizować te pliki, ale na razie ta zmiana zmniejsza presję, ponieważ nie musimy już polegać tylko na nocnych kompilacjach.

Ten fakt - i inne korzyści, takie jak mniej ścisłe sprzężenie, lepsza możliwość ponownego użycia itp. - również pokazał, że spędzanie czasu na dzieleniu „stałych” wcale nie było takie złe ;-)

Aktualizacja 2

Ponieważ na to pytanie wciąż zwraca się uwagę:
Oto, co robiłem przez ostatnie kilka lat:

Umieść każdą stałą, zmienną itp. Dokładnie w odpowiednim dla niej zakresie: Jeśli używasz stałej tylko w jednej metodzie, możesz ją zdefiniować w tej metodzie. Jeśli zainteresowana jest jedna klasa, pozostaw ją jako prywatny szczegół implementacyjny tej klasy. To samo dotyczy przestrzeni nazw, modułu, projektu i zakresu firmy. Używam tego samego wzoru dla funkcji pomocniczych i tym podobnych. (Może to nie dotyczyć 100%, jeśli opracujesz ramy publiczne).

Dzięki temu zwiększona możliwość ponownego użycia, testowalność i łatwość konserwacji do tego stopnia, że ​​nie tylko spędzasz mniej czasu na kompilacji (przynajmniej w C ++), ale także mniej czasu na naprawianie błędów, co pozostawia więcej czasu na faktyczne opracowanie nowych funkcji. Jednocześnie rozwijanie tych funkcji będzie szybsze, ponieważ łatwiej będzie ponownie wykorzystać więcej kodu. Przewyższa to jakąkolwiek przewagę, jaką może mieć plik stałych centralnych.

Jeśli chcesz dowiedzieć się więcej, zapoznaj się zwłaszcza z zasadą segregacji interfejsu i zasadą pojedynczej odpowiedzialności .

Jeśli się zgadzasz, głosuj za odpowiedzią Caleba, ponieważ ta aktualizacja jest zasadniczo bardziej ogólnym podejściem do tego, co powiedział.


2
osobiście w ogóle nie miałbym tytułu interfejsu użytkownika ani ciągów komunikatów. Chciałbym je w app.config
jk.

1
Podoba mi się to, jak teraz to robisz - rozumiem twoje wady, ale możemy po prostu sobie z tym poradzić.
bigtang

1
Widziałem dokładnie ten sam problem w dużym projekcie Java ... Jeden ogromny interfejs „stałych”, zmień wszystko w nim i poczekaj 15 minut na ponowne skompilowanie zaćmienia. Jestem z Calebem: grupuj stałe tam, gdzie naturalnie należą, blisko kodu, który ich używa. Trzymanie ich oddzielnie, ponieważ są stałymi, jest bezużytecznym ćwiczeniem OCD.
Michael Borgwardt,

Ścisłe połączenie nie stanowi problemu, ponieważ tak naprawdę chcesz. Chcesz, aby jedna zmiana w pliku stałych wpłynęła na możliwie wiele plików źródłowych. (Oczywiście masz również inne problemy).
gnasher729

@ gnasher729 jest to prawdą tylko wtedy, gdy wiele klas używa tej samej stałej. Nigdy nie chciałbyś, aby klasy były ściśle powiązane ze stałymi, z którymi nie są powiązane. Na początku może nie wydawać się problemem, dopóki nie spróbujesz użyć go w innym projekcie bez kopiowania lub uruchamiania izolowanych testów
Tim Meyer,

Odpowiedzi:


29

Stałe specyficzne dla klasy powinny iść w interfejsie tej klasy.

Stałe, które są tak naprawdę opcjami konfiguracji, powinny być częścią klasy konfiguracji. Jeśli dostarczysz akcesoria dla opcji konfiguracji w tej klasie (i użyjesz ich zamiast stałych w innym miejscu), nie będziesz musiał przekompilować całego świata po zmianie kilku opcji.

Stałe, które są współużytkowane między klasami, ale które nie mają być konfigurowalne, powinny mieć rozsądny zakres - spróbuj podzielić je na pliki, które mają określone zastosowania, tak aby poszczególne klasy zawierały tylko to, czego naprawdę potrzebują. To znowu pomoże skrócić czas kompilacji po zmianie niektórych z tych stałych.


1
Jeśli jesteś zainteresowany: zaktualizowałem moje pytanie o to, co osiągnąłem, podążając za twoją odpowiedzią i innymi
Tim Meyer,

6

Powiedziałbym po prostu, że chcesz podzielić swoją ogromną klasę stałych na wiele mniejszych plików, na przykład jeden na formularz. Zapewnia to, że nie masz tak dużej zależności od pliku stałych, więc dodanie lub aktualizacja łańcucha nie wymagałaby wówczas całkowitej ponownej kompilacji. Nadal możesz przechowywać te pliki w centralnej lokalizacji, ale (np.) Mieć 1 plik ze stałymi dla każdego okna dialogowego, odpowiednio nazwany. Następnie można uwzględnić tylko te pliki w odpowiednich plikach dialogowych, co znacznie ogranicza rekompilację.

Sugerowałbym również, abyś używał narzędzi GNU GetText do obsługi ciągów, jest przeznaczony do tłumaczenia, ale działa równie dobrze, po prostu zmieniając tekst na coś innego. Możesz umieścić je w zasobach ciągów, ale uważam, że trudniej z nimi pracować, ponieważ są one identyfikowane przez identyfikator, narzędzia GetText są wpisywane przez oryginalny ciąg - ułatwia to tworzenie rzeczy.


Mogę spróbować. Ponieważ klasa stałych z tytułami itp. Jest podzielona na kilka struktur, na początku mógłbym zrobić jedną klasę na strukturę. Nie jestem pewien co do GNU, zwykle nie chcemy zmieniać naszych ciągów w czasie wykonywania, tylko w czasie programowania. Korzystamy jednak z mechanizmu tłumaczenia Qt, na wypadek gdybyśmy musieli w przyszłości dokonać tłumaczenia na inny język.
Tim Meyer,

Jeśli jesteś zainteresowany: zaktualizowałem moje pytanie o to, co osiągnąłem, podążając za twoją odpowiedzią i innymi
Tim Meyer,

2

Uwaga: nie jestem programistą w C ++ ... ale oto moja myśl: należy wziąć pod uwagę komentarz @ jk na temat różnicy między używaniem plików konfiguracyjnych. W DotNet istnieje plik zasobów, który służy do przechowywania takich informacji. W Windows Forms plik zasobów jest utrzymywany z VS dla każdego formularza.

Nie widzę wartości stałej, która mogłaby zostać umieszczona poza jej zakresem użycia, chyba że jest to stała globalna, którą należy udostępnić. Jak wspomniałeś, będzie to trudne do utrzymania przynajmniej podczas rozwoju. Ponadto mogą wystąpić konflikty nazw. Inną sprawą jest to, że może być trudno ustalić, kto używa danej stałej.

Teraz, jeśli chcesz, aby nieprogramiści sprawdzali informacje, w przypadku GUI przechwytujesz dla nich ekran. Jeśli chcesz, aby przeglądali wpisy tabeli danych, możesz wyeksportować dane do Excela lub czegoś podobnego.

Jeśli nadal chcesz korzystać ze scentralizowanego podejścia do lokalizacji i chcesz umieścić wszystkie swoje stałe w jednym dużym pliku, każdy programista może użyć udostępnionego pliku, który jest aktualizowany na koniec każdego interwału, do pliku centralnego. Dane będą pochodzić z poszczególnych plików wykorzystywanych podczas programowania. Można to łatwo zautomatyzować lub wykonać ręcznie. Jednak, jak powiedziałem, jest to prawdopodobnie ryzyko, którego nie musisz podejmować.


2

Nie ma ogólnego rozwiązania. Zadaj sobie pytanie o wydajność, użyteczność, bezpieczeństwo i cykl życia stałej.

Im bliżej są zdefiniowane, tym wyższa jest wydajność.

Im bardziej są pogrupowane logicznie i poza ich zakresem, tym większa możliwość ponownego użycia.

Im mniej dostępne są costant, tym większe bezpieczeństwo.

Im wyższa trwałość stałej, tym mniej zależy jej na tym, gdzie umieścisz ją dla użyteczności.

Stała jak numer wersji zostanie zdefiniowana w jakimś manifeście. Kod błędu funkcji błędu zostanie zdefiniowany wewnątrz klasy. Kod błędu to prawdopodobnie coś o długim okresie użytkowania (= prawie nigdy się nie zmienia). Umieszczenie go w stałym pliku powoduje jedynie spamowanie niepotrzebnych plików.

Im mniej stała ma charakter stałej, ale zmiennej (np. Numer wersji), tym więcej można ją umieścić na zewnątrz. Im mniej zmienna jest stała, więc im bardziej jest stała, tym bardziej powinna być umieszczona w swoim zakresie. Podczas debugowania warto umieścić go na zewnątrz, aby skrócić czas kompilacji.

Jednak twoim początkowym problemem jest czas kompilacji. Pytanie brzmi, czy zadajesz właściwe pytanie. Jeśli czas kompilacji Twojej aplikacji jest zbyt długi, powinieneś pomyśleć o tym, aby uczynić ją bardziej modułową, aby części działały niezależnie od siebie. Częściowo go skompiluj i przetestuj swoje rzeczy niezależnie. Jeśli twoje testy jednostkowe są poprawnie wykonane i są w pełni uruchomione (co jest naprawdę dużym nakładem pracy), możesz dość łatwo zmieniać rzeczy bez martwienia się o to. A potem pytanie staje się zupełnie inne.


1

Sugerowałbym umieszczenie wszystkich tych stałych w jakimś pliku konfiguracyjnym. W przypadku aplikacji Java zwykle używamy plików .properties, prostego tekstu z każdą linią sformatowaną jako „(klucz) = (wartość)”. Przykład

MainPanel.Title = Witamy w naszej aplikacji
DB.table.users = TBL_USERS
login.filename = application.log

Następnie ładujesz ten plik w czasie wykonywania, zapełniasz pamięć podręczną, która pozwala wyszukać klucz i odzyskać wartość. Gdy potrzebujesz stałej, przeszukujesz pamięć podręczną. Nadal będziesz musiał mieć gdzieś swoje klucze, a pamięć podręczna będzie musiała być globalnie dostępna, ale po zmianie rzeczywistych wartości stałych nie jest konieczna ponowna kompilacja, powinno być możliwe ponowne uruchomienie aplikacji (lub jeśli naprawdę chcesz się zachwycić, mieć wiele plików .properties i pamięci podręcznych i dać aplikacji możliwość ponownego załadowania pamięci podręcznej w czasie wykonywania).

Dla implementacji znalazłem to SO: https://stackoverflow.com/questions/874052/properties-file-library-for-c-or-c (było to pierwsze trafienie w wyszukiwarce Google - nie faktycznie korzystałem z tego oprogramowania).


W przypadku projektów C ++ musimy tylko ponownie skompilować plik stałych, jeśli zmienimy wartość. Jest tak, ponieważ wartości są przypisywane w pliku .cpp. Jednak dodanie / usunięcie / zmiana nazwy / przeniesienie stałej wciąż wymaga pełnej przebudowy
Tim Meyer,

@TimMeyer: Jeśli podzielisz stałe na wiele plików, dodanie / usunięcie stałej wpłynie tylko na pliki zależne od tego konkretnego pliku, prawda?
FrustratedWithFormsDesigner

Poprawny. Główny problem polega na tym, że jeśli sugeruję, ludzie zwykle wspominają o jednej z rzeczy, które wymieniłem jako „zalety”, gubią się
Tim Meyer,

Jednak tak naprawdę nie myślałem o umieszczeniu kilku plików stałych w tym samym miejscu
Tim Meyer,

+1. @Tim Meyer: W tym celu sprawdzanie czasu kompilacji kosztuje znacznie więcej niż oszczędza. Poza tym, co zamierzasz zrobić, jeśli potrzebujesz internacjonalizacji?
kevin cline,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.