Ciągi kodujące na stałe, które nigdy się nie zmienią

39

Tak więc, starając się napisać program do koniugacji czasowników (algorytmicznie, nie poprzez zbiór danych) dla języka francuskiego, natknąłem się na niewielki problem.

Algorytm koniugacji czasowników jest właściwie dość prosty w przypadku 17 lub więcej przypadków czasowników i działa według określonego wzorca dla każdego przypadku; dlatego sufiksy koniugacji dla tych 17 klas są statyczne i (najprawdopodobniej) nie zmienią się w najbliższym czasie. Na przykład:

// Verbs #1 : (model: "chanter")
    terminations = {
        ind_imp: ["ais", "ais", "ait", "ions", "iez", "aient"],
        ind_pre: ["e", "es", "e", "ons", "ez", "ent"],
        ind_fut: ["erai", "eras", "era", "erons", "erez", "eront"],
        participle: ["é", "ant"]
    };

Są to fleksyjne przyrostki dla najczęstszej klasy czasowników w języku francuskim.

Istnieją inne klasy czasowników (nieregularności), których koniugacje najprawdopodobniej pozostaną statyczne przez następne stulecie lub dwa. Ponieważ są one nieregularne, ich całkowite koniugacje muszą zostać uwzględnione statycznie, ponieważ nie można w sposób niezawodny koniugować ze wzoru (istnieją również [według moich obliczeń] 32 nieregularności). Na przykład:

// "être":
    forms = {
        ind_imp: ["étais", "étais", "était", "étions", "étiez", "étaient"],
        ind_pre: ["suis", "es", "est", "sommes", "êtes", "sont"],
        ind_fut: ["serai", "seras", "sera", "serons", "serez", "seront"],
        participle: ["été", "étant"]
    };

Mógłbym umieścić to wszystko w XML lub nawet JSON i przekształcić z postaci szeregowej, kiedy trzeba go użyć, ale czy jest sens? Ciągi te są częścią języka naturalnego, który się zmienia, ale w wolnym tempie.

Obawiam się, że robiąc rzeczy „we właściwy sposób” i deserializując niektóre źródła danych, nie tylko skomplikowałem problem, który nie musi być skomplikowany, ale całkowicie cofnąłem się pod kątem całego celu podejście algorytmiczne: nie używać źródła danych! W języku C # mógłbym po prostu stworzyć klasę pod namespace Verb.Conjugation(np. class Irregular), Aby przechowywać te ciągi w wyliczonym typie lub coś, zamiast upychać je w XML i tworzyć class IrregularVerbDeserializer.

Pytanie brzmi : czy właściwe jest stosowanie ciągów kodu, których zmiana jest bardzo mało prawdopodobna w trakcie życia aplikacji? Oczywiście nie mogę zagwarantować 100%, że się nie zmienią, ale ryzyko w porównaniu do kosztu jest prawie trywialne w moich oczach - kodowanie na stałe jest tutaj lepszym pomysłem.

Edycja : proponowany duplikat pyta, jak przechowywać dużą liczbę ciągów statycznych , a moje pytanie brzmi, kiedy powinienem na stałe zakodować te ciągi statyczne .

Chris Cirefice
źródło
26
Czy w przyszłości chcesz używać tego oprogramowania w innym języku niż francuski?
10
Podejście algorytmiczne czy nie, jasne jest, że po prostu trzeba na stałe zakodować te 32 * 20 ciągów (i więcej, gdy dodaje się więcej języków), a jedynym prawdziwym pytaniem jest, gdzie je umieścić. Wybrałbym, gdziekolwiek najbardziej ci odpowiada, co na razie brzmi jak w kodzie. Zawsze możesz je przetasować później.
Ixrec
1
@ChrisCirefice To dla mnie brzmi optymalnie. Idź po to.
Ixrec
2
@Gusdor Nie sądzę, abyś czytał wyraźnie - powiedziałem, że wzory koniugacji prawdopodobnie nigdy się nie zmienią lub zmieniają tak rzadko, że rekompilacja co około 100 lat byłaby w porządku. Oczywiście kod się zmieni, ale kiedy łańcuchy będą tam, jak ich chcę, chyba że dokonam refaktoryzacji, będą one statyczne przez następne 100 lat.
Chris Cirefice
1
+1. Nie wspominając o tym, że za 60-100 lat koszt albo nie będzie istniał, albo zostanie zastąpiony przez lepszą wersję.
HarryCBurn

Odpowiedzi:

56

czy właściwe jest stosowanie ciągów kodu, których zmiana jest mało prawdopodobna w trakcie życia aplikacji? Oczywiście nie mogę zagwarantować 100%, że się nie zmienią, ale ryzyko w porównaniu do kosztu jest prawie banalne w moich oczach - kodowanie jest tutaj lepszym pomysłem

Wydaje mi się, że odpowiedziałeś na własne pytanie.

Jednym z największych wyzwań, przed którymi stoimy, jest oddzielenie rzeczy, które mogą się zmienić od rzeczy, które się nie zmienią. Niektórzy ludzie wariują i zrzucają absolutnie wszystko, co mogą, do pliku konfiguracyjnego. Inni przechodzą na drugą skrajność i wymagają rekompilacji nawet w przypadku najbardziej oczywistych zmian.

Wybrałbym najprostsze podejście do wdrożenia, dopóki nie znalazłem przekonującego powodu, aby uczynić go bardziej skomplikowanym.

Dan Pichelman
źródło
Dzięki Dan, to właśnie tak wymyśliłem. Napisanie w tym celu schematu XML, posiadanie innego pliku do śledzenia i napisanie interfejsu do deserializacji danych wydawało się przesadą, biorąc pod uwagę, że po prostu nie ma tak wielu ciągów, a ponieważ jest to naturalny język, jest mało prawdopodobne, aby drastycznie się zmienił w ciągu następnych 100 lat. Na szczęście w dzisiejszych językach programowania mamy wymyślne sposoby na wyodrębnienie tych surowych danych za ładnie wyglądającym interfejsem, na przykład, French.Verb.Irregular.Etrektóre zawierałyby dane z mojego pytania. Myślę, że wszystko działa dobrze;)
Chris Cirefice
3
+1 Tutaj, z obozu Ruby, zacząłem pisać na sztywno rzeczy i w razie potrzeby przenieść je do konfiguracji Nie przedwcześnie przeprojektowuj swój projekt, konfigurując go. To po prostu cię spowalnia.
Overbryd
2
Uwaga: niektóre grupy mają inną definicję „twardego kodowania”, więc pamiętaj, że ten termin oznacza wiele rzeczy. Istnieje dobrze rozpoznany anty-wzorzec, w którym kodujesz wartości w instrukcjach funkcji, zamiast tworzyć struktury danych takie, jakie masz ( if (num == 0xFFD8)). Ten przykład powinien stać się podobny if (num == JPEG_MAGIC_NUMBER)do niemal wszystkich przypadków ze względu na czytelność. Po prostu zwracam na to uwagę, ponieważ słowo „twarde kodowanie” często podnosi włosy na szyjach ludzi (takich jak moje) z powodu tego alternatywnego znaczenia tego słowa.
Cort Ammon
@CortAmmon JPEG ma wiele magicznych liczb. Na pewno JPEG_START_OF_IMAGE_MARKER?
user253751
@immibis Twój wybór stałego nazewnictwa jest prawdopodobnie lepszy niż mój.
Cort Ammon
25

Rozumujesz w złym zakresie.

Nie zakodowałeś tylko pojedynczych czasowników. Zakodowałeś język i jego zasady . To z kolei oznacza, że ​​aplikacji nie można używać w żadnym innym języku i nie można jej rozszerzać o inne reguły.

Jeśli taki jest twój zamiar (tj. Użycie go tylko w języku francuskim), jest to właściwe podejście, ze względu na YAGNI. Ale przyznajesz się, że chcesz użyć go później również w innych językach, co oznacza, że ​​już wkrótce będziesz musiał przenieść całą zakodowaną część do plików konfiguracyjnych. Pozostałe pytanie to:

  • Czy, z pewnością blisko 100%, w najbliższej przyszłości rozszerzysz aplikację na inne języki? Jeśli tak, powinieneś teraz eksportować rzeczy do plików JSON lub XML (dla słów, części słów itp.) I dynamicznych języków (dla reguł) zamiast zmuszać się do przepisania większej części swojej aplikacji.

  • Czy jest tylko niewielkie prawdopodobieństwo, że aplikacja zostanie gdzieś rozszerzona w przyszłości, w takim przypadku YAGNI dyktuje, że najprostsze podejście (to, z którego teraz korzystasz) jest lepsze?

Jako przykład weźmy sprawdzanie pisowni Microsoft Word. Jak myślisz, ile rzeczy jest zakodowanych na stałe?

Jeśli tworzysz procesor tekstu, można zacząć od prostego silnika ortograficzny sztywno reguł i nawet Hardcoded słowami: if word == "musik": suggestSpelling("music");. Szybko zaczniesz przesuwać słowa, a potem będziesz rządzić się poza swoim kodem. Inaczej:

  • Za każdym razem, gdy musisz dodać słowo, musisz je ponownie skompilować.
  • Jeśli nauczyłeś się nowej reguły, musisz ponownie zmienić kod źródłowy.
  • Co ważniejsze, nie ma możliwości dostosowania silnika do języka niemieckiego lub japońskiego bez pisania ogromnych ilości kodu.

Jak sam się podkreśliłeś:

Bardzo niewiele zasad z francuskiego można zastosować do japońskiego.

Jak tylko kodujesz reguły dla każdego języka, każdy inny będzie wymagał coraz większej ilości kodu, szczególnie biorąc pod uwagę złożoność języków naturalnych.

Innym tematem jest sposób wyrażania tych różnych zasad, jeśli nie za pomocą kodu. Ostatecznie może się okazać, że język programowania jest najlepszym narzędziem do tego. W takim przypadku, jeśli trzeba rozszerzyć silnik bez jego ponownej kompilacji, dynamiczne języki mogą być dobrą alternatywą.

Arseni Mourzenko
źródło
1
Cóż, oczywiście, że nie wszystko jest tam na stałe zakodowane: P, więc myślę, że tak naprawdę sprowadza się to do ustalenia, jak chcę wyglądać interfejs, aby móc go zastosować w wielu językach. Problem polega na tym, że nie znam jeszcze wystarczająco dobrze wszystkich języków, więc jest to faktycznie niemożliwe. Myślę, że jedyną rzeczą, która może jesteś brakujące jest to, że wzory koniugacji (to wszystko mówię) są bardzo statyczne w języku, a to naprawdę jest coś, co jest indywidualnie dla każdego przypadku. Istnieje około 17 wzorów koniugacji w języku francuskim dla czasowników. To się wkrótce nie wydłuży ...
Chris Cirefice,
4
Nie zgadzam się - nie sądzę, aby sensowne było przenoszenie czegokolwiek poza kod, zanim nastąpi to naturalnie przez refaktoryzację. Zacznij od jednego języka, dodaj inne - w pewnym momencie implementacja ILanguageRule będzie współdzielić wystarczającą ilość kodu, aby bardziej efektywne było sparametryzowanie pojedynczej implementacji za pomocą XML (lub innego pliku). Ale nawet wtedy możesz skończyć z japońskim, który ma zupełnie inną strukturę. Zaczynając od przekazania interfejsu do formatu XML (lub podobnego), błaga tylko o zmiany w interfejsie, a nie o implementację.
ptyx
2
Uwaga: jeśli chcesz dodać więcej języków, nie oznacza to przeniesienia języków do pliku konfiguracyjnego! Równie dobrze możesz mieć LanguageProcessorklasę z wieloma podklasami. (W rzeczywistości „plik konfiguracyjny” jest w rzeczywistości klasą)
użytkownik253751
2
@MainMa: Dlaczego uważasz, że rekompilacja podczas dodawania słowa jest problemem? Trzeba przebudować robiąc żadnych innych zmian w kodzie i tak, i lista słów jest chyba część kodu, która jest najmniej prawdopodobne, aby zmieniać z upływem czasu.
JacquesB
3
Podejrzewam, że elastyczność możliwości kodowania bardzo specyficznych reguł gramatycznych każdego języka w podklasie byłaby w końcu wygodniejsza niż możliwość w jakiś sposób załadowania tych samych reguł z pliku konfiguracyjnego (ponieważ w zasadzie piszesz swój własny język programowania do interpretacji konfiguracji).
David K
15

Ciągi powinny zostać wyodrębnione do pliku konfiguracyjnego lub bazy danych, gdy wartości mogą ulec zmianie niezależnie od logiki programu.

Na przykład:

  • Wyodrębnianie tekstów interfejsu użytkownika do plików zasobów. Pozwala to nie-programistom edytować i sprawdzać teksty oraz umożliwia dodawanie nowych języków poprzez dodawanie nowych zlokalizowanych plików zasobów.

  • Wyodrębnianie parametrów połączenia, adresów URL do usług zewnętrznych itp. Do plików konfiguracyjnych. Pozwala to na użycie różnych konfiguracji w różnych środowiskach oraz na zmianę konfiguracji w locie, ponieważ mogą one wymagać zmiany z przyczyn niezależnych od aplikacji.

  • Sprawdzanie pisowni ze słownikiem słów do sprawdzenia. Możesz dodawać nowe słowa i języki bez modyfikowania logiki programu.

Ale jest też narzut związany z rozpakowywaniem do konfiguracji i nie zawsze ma to sens.

Ciągi mogą być zakodowane na stałe, gdy rzeczywisty ciąg nie może się zmienić bez zmiany logiki programu.

Przykłady:

  • Kompilator dla języka programowania. Słowa kluczowe nie są wyodrębniane do konfiguracji, ponieważ każde słowo kluczowe ma określoną semantykę, która musi być obsługiwana przez kod w kompilatorze. Dodanie nowego słowa kluczowego zawsze będzie wymagało zmian w kodzie, więc nie będzie żadnej wartości przy wyodrębnianiu ciągów do pliku konfiguracyjnego.
  • Wdrażanie protokołu: np. klient HTTP będzie miał zapisane na stałe ciągi, takie jak „GET”, „typ zawartości” itp. Tutaj ciągi te są częścią specyfikacji protokołu, więc są częściami kodu, które najprawdopodobniej się nie zmienią.

W twoim przypadku myślę, że jasne jest, że słowa są integralną częścią logiki programu (ponieważ budujesz koniugator z określonymi regułami dla określonych słów), a wyodrębnianie tych słów do zewnętrznego pliku nie ma żadnej wartości.

Jeśli dodasz nowy język, i tak będziesz musiał dodać nowy kod, ponieważ każdy język ma określoną logikę koniugacji.


Niektórzy sugerują, że można dodać silnik reguł, który pozwala określić reguły koniugacji dla dowolnych języków, aby nowe języki można było dodawać wyłącznie przez konfigurację. Zastanów się bardzo, zanim pójdziesz tą drogą, ponieważ ludzkie języki są cudownie dziwne, więc potrzebujesz bardzo wyrazistego silnika reguł. Zasadniczo wymyśliłbyś nowy język programowania (koniugacja DSL) dla wątpliwych korzyści. Ale masz już do dyspozycji język programowania, który może zrobić wszystko, czego potrzebujesz. W każdym razie YAGNI.

JacquesB
źródło
1
Właściwie w komentarzu do MainMa wspomniałem, że napisanie DSL w tym celu byłoby bezcelowe, ponieważ bardzo niewiele języków naturalnych jest wystarczająco podobnych, aby było warto. Może francuski / hiszpański / włoski byłby wystarczająco blisko , ale nie byłby wart dodatkowego wysiłku, biorąc pod uwagę, że ilość reguł jest bardzo statyczna w danym języku. Inne kwestie, które wspomniałeś o złożoności, to moje obawy i myślę, że wspaniale zrozumiałeś to, o co pytam w swoim pytaniu i dałeś świetną odpowiedź wraz z przykładami, więc daj +1!
Chris Cirefice,
5

Zgadzam się w 100% z odpowiedzią Dana Pichelmana, ale chciałbym dodać jedną rzecz. Pytanie, które powinieneś sobie zadać, brzmi: „kto będzie utrzymywać / rozszerzać / poprawiać listę słów?”. Jeśli zawsze jest to osoba, która również przestrzega zasad określonego języka (chyba konkretnego programisty), to nie ma sensu korzystać z zewnętrznego pliku konfiguracyjnego, jeśli to komplikuje sprawę - nie uzyskasz żadnych korzyści z to. Z tego punktu widzenia sensowne jest kodowanie takich list słów, nawet jeśli trzeba je od czasu do czasu zmieniać, o ile wystarczy dostarczyć nową listę jako część nowej wersji.

(Z drugiej strony, jeśli istnieje niewielka szansa, że ​​ktoś inny będzie w stanie utrzymać listę w przyszłości lub jeśli chcesz zmienić listy słów bez wdrażania nowej wersji aplikacji, użyj osobnego pliku).

Doktor Brown
źródło
To dobra uwaga - jednak bardzo prawdopodobne jest, że będę jedyną osobą, która faktycznie utrzyma kod, przynajmniej przez kilka następnych lat. Zaletą tego jest to, że chociaż ciągi będą zakodowane na sztywno, to jest to bardzo mały zestaw ciągów / reguł, które prawdopodobnie nie zmienią się w najbliższym czasie (ponieważ jest to naturalny język, który nie ewoluuje zbytnio z roku na rok -rok). To powiedziawszy, zasady koniugacji, ciągi terminacji czasowników itp. Najprawdopodobniej będą takie same dla naszego życia :)
Chris Cirefice
1
@ChrisCirefice ": dokładnie o to mi chodzi.
Doc Brown
2

Nawet podczas hardcoding wydaje się dobrze tutaj, i lepiej niż dynamiczne ładowanie plików konfiguracyjnych, nadal polecam, że nie ściśle oddzielić swoje dane (słownik czasowników) od algorytmu . Możesz je skompilować bezpośrednio w aplikacji w procesie kompilacji.

Pozwoli ci to zaoszczędzić sporo mgiełki dzięki utrzymywaniu listy. W swoim VCS możesz łatwo stwierdzić, czy zatwierdzenie zmieniło algorytm, czy po prostu naprawić błąd koniugacji. Ponadto lista może wymagać dołączenia w przyszłości w przypadkach, które nie zostały wzięte pod uwagę. Zwłaszcza liczba 32 czasowników nieregularnych, które policzyłeś, nie wydaje się dokładna. Chociaż wydają się one obejmować powszechnie stosowane, znalazłem odniesienia do 133, a nawet 350 z nich.

Bergi
źródło
Bergi, planowałem oddzielić dane od algorytmu. Co zauważasz na temat francuskich nieprawidłowości - definicja nieregularności jest w najlepszym razie źle zrozumiana. Mam na myśli, gdy mówię nieregularne, czasowniki, których nie można „obliczyć” ani skoniugować z samej ich bezokolicznikowej formy. Czasowniki nieregularne nie mają żadnego szczególnego wzorca, a zatem muszą mieć wyraźnie wymienione koniugacje (na przykład w French.Verb.Conjugation.Irregular`). Technicznie rzecz biorąc, -ir czasowniki są „nieregularne”, ale w rzeczywistości mają ustalony wzór koniugacji :)
Chris Cirefice
0

Ważną częścią jest rozdzielenie obaw. Jak to osiągnąć, jest mniej istotne. tzn. Java jest w porządku.

Niezależnie od tego, w jaki sposób wyrażane są reguły, należy dodać język zmiany reguły: ile kodu i plików trzeba edytować?

Idealnie byłoby, gdyby dodanie nowego języka było możliwe poprzez dodanie pliku „english.xml” lub nowego „EnglishRules implementuje obiekt ILanguageRules”. Plik tekstowy (JSON / XML) daje przewagę, jeśli chcesz go zmienić poza cyklem życia kompilacji, ale wymaga złożonej gramatyki, analizy i trudniej będzie go debugować. Plik kodu (Java) pozwala wyrazić złożone reguły w prostszy sposób, ale wymaga przebudowy.

Zaczynam od prostego API Java za czystym językiem agnostycznym interfejsem - ponieważ jest to potrzebne w obu przypadkach. Zawsze możesz dodać implementację tego interfejsu zabezpieczoną plikiem XML później, jeśli chcesz, ale nie widzę potrzeby rozwiązania tego problemu od razu (ani nigdy).

ptyx
źródło