Zamknięte . To pytanie musi być bardziej
skoncentrowane . Obecnie nie przyjmuje odpowiedzi.
Komentarze
Odpowiedź
Problem z obsługą kodu korzystającego z flag polega na tym, że liczba stanów szybko rośnie i prawie zawsze są stany nieobsługiwane. Jeden przykład z własnego doświadczenia: pracowałem nad kodem, który miał te trzy flagi
bool capturing, processing, sending;
Te trzy utworzyły osiem stanów (właściwie były dwie inne flagi także). Nie wszystkie możliwe kombinacje wartości zostały uwzględnione w kodzie, a użytkownicy zauważyli błędy:
if(capturing && sending){ // we must be processing as well ... }
Okazało się, że zdarzały się sytuacje, w których założenie w instrukcji if powyżej było fałszem.
Flagi mają tendencję do narastania w czasie i ukrywają rzeczywisty stan klasy. Dlatego należy ich unikać.
Komentarze
Odpowiedź
Oto przykład, kiedy flagi są przydatne.
Mam fragment kodu, który generuje hasła (przy użyciu bezpiecznego kryptograficznie generatora liczb pseudolosowych). Osoba wywołująca metodę wybiera, czy hasło nie powinno zawierać wielkich liter, małych liter, cyfr, symboli podstawowych, symboli rozszerzonych, symboli greckich, cyrylicy i unikodu.
Z flagami wywołanie tej metody jest łatwe:
var password = this.PasswordGenerator.Generate( CharacterSet.Digits | CharacterSet.LowercaseLetters | CharacterSet.UppercaseLetters);
i można to nawet uprościć do:
var password = this.PasswordGenerator.Generate(CharacterSet.LettersAndDigits);
Jaka byłaby sygnatura metody bez flag?
public byte[] Generate( bool uppercaseLetters, bool lowercaseLetters, bool digits, bool basicSymbols, bool extendedSymbols, bool greekLetters, bool cyrillicLetters, bool unicode);
nazywa się tak:
// Very readable, isn"t it? // Tell me just by looking at this code what symbols do I want to be included? var password = this.PasswordGenerator.Generate( true, true, true, false, false, false, false, false);
Jak zauważono w komentarzach, innym podejściem byłoby użycie kolekcji:
var password = this.PasswordGenerator.Generate( new [] { CharacterSet.Digits, CharacterSet.LowercaseLetters, CharacterSet.UppercaseLetters, });
Jest to dużo bardziej czytelne w porównaniu z zestawem true
i false
, ale nadal ma dwie wady:
Główną wadą jest to, że w celu dopuszczenia połączonych wartości, takich jak CharacterSet.LettersAndDigits
napisałbyś coś takiego w metodzie Generate()
:
if (set.Contains(CharacterSet.LowercaseLetters) || set.Contains(CharacterSet.Letters) || set.Contains(CharacterSet.LettersAndDigits) || set.Contains(CharacterSet.Default) || set.Contains(CharacterSet.All)) { // The password should contain lowercase letters. }
prawdopodobnie przepisany w ten sposób:
var lowercaseGroups = new [] { CharacterSet.LowercaseLetters, CharacterSet.Letters, CharacterSet.LettersAndDigits, CharacterSet.Default, CharacterSet.All, }; if (lowercaseGroups.Any(s => set.Contains(s))) { // The password should contain lowercase letters. }
Porównaj to z tym, co masz, używając flag:
if (set & CharacterSet.LowercaseLetters == CharacterSet.LowercaseLetters) { // The password should contain lowercase letters. }
s Po drugie, bardzo drobną wadą jest to, że nie jest jasne, jak zachowałaby się metoda, gdyby została wywołana w ten sposób:
var password = this.PasswordGenerator.Generate( new [] { CharacterSet.Digits, CharacterSet.LettersAndDigits, // So digits are requested two times. });
Komentarze
Odpowiedź
Ogromnym blokiem funkcyjnym jest zapach a nie flagi. Jeśli ustawisz flagę w linii 5, to sprawdzisz tylko flagę w linii 354, to jest źle. Jeśli ustawisz flagę w linii 8 i sprawdzisz flagę w linii 10, to nie ma sprawy. Ponadto jedna lub dwie flagi na blok kodu są w porządku, 300 flag w funkcji jest złych.
Odpowiedź
Zwykle flagi można całkowicie zastąpić jakimś smakiem wzorca strategii, z jedną implementacją strategii dla każdej możliwej wartości flagi. To sprawia, że dodawanie nowego zachowania jest o wiele łatwiejsze.
W sytuacjach krytycznych dla wydajności koszt pośredniego może pojawić się i sprawić, że konieczna będzie dekonstrukcja na wyraźne flagi. Biorąc to pod uwagę, mam problem z zapamiętaniem pojedynczego przypadku, w którym faktycznie musiałem to zrobić.
Odpowiedź
Nie, Flagi nie są złe ani złe, które należy zreformować za wszelką cenę.
Rozważmy Pattern.compile (wyrażenie regularne, flagi int)
zadzwoń. To jest tradycyjna maska bitowa i działa. Rzuć okiem na stałe w java i wszędzie tam, gdzie zobaczysz zestaw 2 n , wiesz, że są tam flagi.
W idealnym świecie refaktoryzacji należałoby zamiast tego użyć EnumSet , gdzie stałe są zamiast tego wartościami w wyliczeniu i zgodnie z dokumentacją:
Wydajność przestrzenna i czasowa tej klasy powinna być wystarczająco dobra, aby umożliwić jej użycie jako wysokiej jakości, bezpiecznej alternatywy dla tradycyjnych „flag bitowych” opartych na int.
W idealnym świecie wywołanie Pattern.compile staje się Pattern.compile(String regex, EnumSet<PatternFlagEnum> flags)
.
Wszystko to powiedziawszy, jego nadal flagi.O wiele łatwiej jest pracować z Pattern.compile("foo", Pattern.CASE_INSENSTIVE | Pattern.MULTILINE)
niż mieć Pattern.compile("foo", new PatternFlags().caseInsenstive().multiline())
lub inny styl próbowania robienia tego, czym naprawdę są flagi i dobre dla.
Flagi są często widoczne podczas pracy z rzeczami na poziomie systemu. Podczas interakcji z czymś na poziomie systemu operacyjnego można gdzieś znaleźć flagę – czy będzie to wartość zwracana przez proces, czy uprawnienia pliku, czy też flagi otwierania gniazda. Próba refaktoryzacji tych przypadków podczas polowania na czarownice przeciwko wyczuwalnemu zapachowi kodu prawdopodobnie zakończy się gorszym kodem niż gdyby ktoś zaakceptował i zrozumiał flagę.
Problem pojawia się, gdy ludzie niewłaściwie używają flag, rzucając je razem tworzenie zbioru frankenflag wszelkiego rodzaju niepowiązanych flag lub próbowanie ich użycia tam, gdzie w ogóle nie są.
Odpowiedź
Zakładam, że mówimy o flagach w sygnaturach metod.
Używanie pojedynczej flagi jest wystarczająco złe.
To nie będzie miało żadnego znaczenia dla twoich kolegów, gdy zobaczą to po raz pierwszy. Będą musieli spojrzeć na kod źródłowy metody, aby ustalić, co robi. Prawdopodobnie będziesz w tym samym miejscu kilka miesięcy później, kiedy zapomnisz, o co chodzi w metodzie.
Przekazanie flagi do metody, zwykle oznacza, że metoda jest odpowiedzialna za wiele rzeczy. Wewnątrz metody prawdopodobnie wykonujesz proste sprawdzenie wierszy:
if (flag) DoFlagSet(); else DoFlagNotSet();
To „słaba separacja problemów i zwykle można znaleźć sposób na obejście tego problemu.
Zwykle mam dwie oddzielne metody:
public void DoFlagSet() { } public void DoFlagNotSet() { }
Będzie to miało większy sens w przypadku nazw metod, które mają zastosowanie do problemu, który rozwiązujesz.
Przekazywanie wielu flag jest dwa razy gorsze. Jeśli naprawdę potrzebujesz przekazać wiele flag, rozważ umieszczenie ich w klasie. Nawet wtedy nadal będziesz mieć ten sam problem, ponieważ Twoja metoda prawdopodobnie robi wiele rzeczy.
Odpowiedź
Flagi i większość zmiennych temp to silny zapach. Najprawdopodobniej można by je refaktoryzować i zastąpić metodami zapytań.
Poprawiono:
Flagi i zmienne tymczasowe przy wyrażaniu stanu powinny być refaktoryzowane na metody zapytań. Wartości stanu (wartości logiczne, ints i inne prymitywy) powinny prawie -zawsze- być ukryte jako część szczegółów implementacji.
Flagi używane do sterowania, routingu i ogólnego przepływu programu mogą również wskazywać możliwość refaktoryzacji sekcji struktur kontrolnych na oddzielne strategie lub fabryki, lub cokolwiek, co może być odpowiednie w danej sytuacji, które nadal korzystają z metod zapytań.
Odpowiedź
Kiedy mówimy o flagach, powinniśmy wiedzieć, że będą one modyfikowane w czasie wykonywania programu i że będą wpływać na zachowanie programu na podstawie ich stanów. Dopóki mamy dobrą kontrolę nad tymi dwoma rzeczami, będą one działać świetnie.
Flagi mogą działać świetnie, jeśli
- Zdefiniowałeś je w odpowiednim zakresie. Przez odpowiednie rozumiem, że zakres nie powinien zawierać żadnego kodu, który nie musi / nie powinien ich modyfikować. Lub przynajmniej kod jest bezpieczny (na przykład nie może być wywoływany bezpośrednio z zewnątrz)
- Jeśli istnieje potrzeba obsługi flag z zewnątrz i jeśli jest wiele flag, możemy zakodować obsługę flag jako jedyny sposób bezpiecznie modyfikować flagi. Ten program obsługi flag może sam hermetyzować flagi i metody ich modyfikowania. Można go następnie uczynić singletonem, a następnie udostępnić między klasami, które potrzebują dostępu do flag.
- I wreszcie, aby zachować łatwość obsługi, jeśli jest zbyt wiele flag:
- Nie trzeba mówić, że powinny stosować rozsądne nazewnictwo
- Powinien być udokumentowany, jakie są prawidłowe wartości (mogą być wyliczeniami)
- Powinien być udokumentowany, KTÓRY KOD ZMIENI każdy z nich, a także JAKI STAN spowoduje w przypisaniu określonej wartości do flagi.
- KTÓRY KOD BĘDZIE ZUŻYWAĆ je i JAKIE ZACHOWANIE spowoduje określoną wartość
Jeśli jest dużo flag, dobre prace projektowe powinny poprzedzać, ponieważ flagi zaczynają odgrywać kluczową rolę w zachowaniu programu. Możesz przejść do diagramów stanu do modelowania. Takie diagramy działają również jako dokumentacja i wizualne wskazówki podczas zajmowania się nimi.
Dopóki te rzeczy są na miejscu, myślę, że nie doprowadzi to do bałaganu.
Odpowiedź
Z pytania założyłem, że QA oznacza zmienne flagowe (globalne), a nie bity parametru funkcji.
Są sytuacje gdzie nie masz zbyt wielu innych możliwości. Na przykład bez systemu operacyjnego musisz ocenić przerwania.Jeśli przerwanie pojawia się bardzo często i nie masz czasu na wykonaniedługiej oceny w ISR, jest to nie tylko dozwolone, ale czasami nawet najlepsza praktyka, aby ustawić tylko kilka globalnych flag w ISR (powinieneś spędzić jak najmniej czasu w ISR) i ocenić te flagi w głównej pętli.
Odpowiedź
Nie sądzę cokolwiek jest absolutnym złem w programowaniu.
Jest jeszcze jedna sytuacja, w której flagi mogą być w porządku, nie zostały tu jeszcze wymienione …
Rozważ użycie zamknięć w tym fragmencie kodu JavaScript:
exports.isPostDraft = function ( post, draftTag ) { var isDraft = false; if (post.tags) post.tags.forEach(function(tag){ if (tag === draftTag) isDraft = true; }); return isDraft; }
Przekazywana funkcja wewnętrzna do „Array.forEach”, nie może po prostu „zwrócić prawdy”.
Dlatego musisz utrzymywać stan na zewnątrz za pomocą flagi.