Notka moderatora Na
to pytanie wysłano już siedemnaście odpowiedzi . Zanim opublikujesz nową odpowiedź, przeczytaj istniejące odpowiedzi i upewnij się, że Twój punkt widzenia nie jest odpowiednio uwzględniony.
Postępuję zgodnie z niektórymi praktykami zalecanymi w książce Roberta Martina „Czysty kod”, szczególnie te, które dotyczą tego rodzaju oprogramowania, z którym pracuję, i te, które mają dla mnie sens (nie traktuję tego jako dogmatu) .
Jedną z efektów ubocznych, którą zauważyłem, jest jednak to, że „czysty” kod, który piszę, to więcej kodu, niż gdybym nie przestrzegał pewnych praktyk. Konkretne praktyki, które do tego prowadzą, to:
- Kapsułkowanie warunkowe
Więc zamiast
if(contact.email != null && contact.emails.contains('@')
Mógłbym napisać taką małą metodę
private Boolean isEmailValid(String email){...}
- Zastępowanie komentarza wbudowanego inną metodą prywatną, tak aby nazwa metody opisywała się, zamiast umieszczania na nim komentarza wstawianego
- Klasa powinna mieć tylko jeden powód do zmiany
I kilka innych. Chodzi o to, że to, co może być metodą 30 wierszy, ostatecznie staje się klasą, ze względu na małe metody, które zastępują komentarze i zawierają warunki warunkowe itp. Gdy zdasz sobie sprawę, że masz tak wiele metod, wówczas „sensowne” jest umieść całą funkcjonalność w jednej klasie, a tak naprawdę powinna to być metoda.
Zdaję sobie sprawę, że każda ekstremalna praktyka może być szkodliwa.
Konkretne pytanie, na które szukam odpowiedzi, brzmi:
Czy jest to akceptowalny produkt uboczny pisania czystego kodu? Jeśli tak, to jakich argumentów mogę użyć, aby uzasadnić fakt, że napisano więcej LOC?
Organizacja nie jest specjalnie zainteresowana większą liczbą LOC, ale więcej LOC może skutkować bardzo dużymi klasami (to znowu może zostać zastąpione długą metodą bez wielu funkcji pomocniczych, które kiedyś były używane, ze względu na czytelność).
Kiedy zobaczysz klasę, która jest wystarczająco duża, sprawia wrażenie, że klasa jest wystarczająco zajęta i że jej odpowiedzialność została zakończona. Możesz zatem stworzyć więcej klas, aby osiągnąć inne funkcje. W rezultacie powstaje wiele klas, z których każda robi „jedną rzecz” za pomocą wielu małych metod pomocniczych.
TO jest szczególny problem ... klasy te mogą być pojedynczą klasą, która wciąż osiąga „jedną rzecz”, bez pomocy wielu małych metod. Może to być pojedyncza klasa z 3 lub 4 metodami i pewnymi komentarzami.
źródło
Odpowiedzi:
Ci ludzie poprawnie coś zidentyfikowali: chcą, aby kod był łatwiejszy do utrzymania. Tam, gdzie popełnili błąd, zakłada się, że im mniej kodu, tym łatwiej go utrzymać.
Aby kod był łatwy w utrzymaniu, musi być łatwy do zmiany. Zdecydowanie najłatwiejszym sposobem na uzyskanie łatwego do zmiany kodu jest posiadanie pełnego zestawu zautomatyzowanych testów, które zawiodą, jeśli twoja zmiana jest przełomowa. Testy są kodem, więc ich napisanie powiększy bazę kodu. I to jest dobra rzecz.
Po drugie, aby dowiedzieć się, co należy zmienić, kod musi być łatwy do odczytania i uzasadnienia. Bardzo zwięzły kod, zmniejszony tylko po to, aby utrzymać odliczanie linii, jest bardzo mało czytelny. Oczywiście trzeba znaleźć kompromis, ponieważ czytanie dłuższego kodu zajmie więcej czasu. Ale jeśli łatwiej to zrozumieć, warto. Jeśli nie oferuje tej korzyści, wówczas ta gadatliwość przestaje być korzyścią. Ale jeśli dłuższy kod poprawia czytelność, to znowu dobrze.
źródło
Tak, jest to akceptowalny produkt uboczny, a uzasadnieniem jest to, że jest on teraz tak skonstruowany, że nie musisz czytać większości kodu przez większość czasu. Zamiast czytać funkcję 30-liniową za każdym razem, gdy dokonujesz zmiany, czytasz funkcję 5-liniową, aby uzyskać ogólny przepływ, a może kilka funkcji pomocniczych, jeśli twoja zmiana dotknie tego obszaru. Jeśli twoja nowa „dodatkowa” klasa zostanie wywołana
EmailValidator
i wiesz, że twój problem nie dotyczy sprawdzania poprawności wiadomości e-mail, możesz całkowicie pominąć jej czytanie.Łatwiej jest również ponownie używać mniejszych elementów, co zwykle zmniejsza liczbę linii w całym programie.
EmailValidator
Może być stosowany w każdym miejscu. Niektóre wiersze kodu, które sprawdzają poprawność wiadomości e-mail, ale są usuwane razem z kodem dostępu do bazy danych, nie mogą być ponownie użyte.A następnie zastanów się, co należy zrobić, jeśli zasady sprawdzania poprawności wiadomości e-mail będą kiedykolwiek musiały zostać zmienione - co wolisz: jedna znana lokalizacja; lub wiele lokalizacji, być może brakuje kilku?
źródło
iterations < _maxIterations
do metody o nazwieShouldContinueToIterate
jest głupie .Bill Gates słynął z powiedzenia: „Mierzenie postępu programowania liniami kodu jest jak mierzenie postępu budowy samolotu według masy”.
Pokornie zgadzam się z tym sentymentem. Nie oznacza to, że program powinien dążyć do większej lub mniejszej liczby wierszy kodu, ale nie jest to ostatecznie to, co liczy się do stworzenia działającego i działającego programu. Pomaga pamiętać, że ostatecznie powodem dodania dodatkowych wierszy kodu jest to, że jest on teoretycznie bardziej czytelny w ten sposób.
Można mieć spory co do tego, czy konkretna zmiana jest bardziej lub mniej czytelna, ale nie sądzę, abyś był w błędzie, wprowadzając zmiany w swoim programie, ponieważ tak myślisz, czyniąc to bardziej czytelnym. Na przykład tworzenie an
isEmailValid
może być uważane za zbędne i niepotrzebne, zwłaszcza jeśli zostanie wywołane dokładnie przez klasę, która go definiuje. Wolałbym jednak zobaczyćisEmailValid
warunek niż ciąg warunków AND, w którym muszę określić, co sprawdza każdy warunek i dlaczego jest sprawdzany.Problemem jest tworzenie
isEmailValid
metody, która ma skutki uboczne lub sprawdzanie rzeczy innych niż e-mail, ponieważ jest to gorsze niż zwykłe wypisywanie wszystkich. Jest gorzej, ponieważ wprowadza w błąd i mogę z tego powodu przegapić błąd.Chociaż oczywiście nie robisz tego w tym przypadku, zachęcam więc do kontynuowania. Zawsze powinieneś zadać sobie pytanie, czy dokonując zmiany, łatwiej jest ją przeczytać, a jeśli tak, to zrób to!
źródło
Jest to kwestia utraty wzroku na rzeczywistym celu.
Liczy się obniżenie godzin spędzonych na rozwoju . Jest to mierzone czasem (lub równoważnym wysiłkiem), a nie liniami kodu.
To tak, jakby powiedzieć, że producenci samochodów powinni budować samochody przy użyciu mniejszej liczby śrub, ponieważ włożenie każdej śruby zajmuje niezerową ilość czasu. Chociaż jest to pedantycznie poprawne, wartość rynkowa samochodu nie jest określona przez liczbę śrub lub nie ma. Przede wszystkim samochód musi być wydajny, bezpieczny i łatwy w utrzymaniu.
Reszta odpowiedzi to przykłady tego, jak czysty kod może prowadzić do oszczędności czasu.
Wycięcie lasu
Weź aplikację (A), która nie ma logowania. Teraz utwórz aplikację B, która jest tą samą aplikacją A, ale z logowaniem. B zawsze będzie zawierało więcej wierszy kodu, dlatego musisz napisać więcej kodu.
Ale dużo czasu poświęci się na zbadanie problemów i błędów oraz ustalenie, co poszło nie tak.
W przypadku aplikacji A programiści utkną w czytaniu kodu i będą musieli nieustannie odtwarzać problem i przechodzić przez kod, aby znaleźć źródło problemu. Oznacza to, że programista musi przetestować od początku wykonania do końca, w każdej użytej warstwie, i musi obserwować każdą wykorzystaną logikę.
Może ma szczęście, że natychmiast go znalazł, ale może odpowiedź znajdzie się w ostatnim miejscu, o którym myśli.
W przypadku aplikacji B, zakładając idealne rejestrowanie, programista obserwuje dzienniki, może natychmiast zidentyfikować wadliwy komponent i teraz wie, gdzie szukać.
Może to być kwestia zaoszczędzonych minut, godzin lub dni; w zależności od wielkości i złożoności bazy kodu.
Regresje
Weź aplikację A, która wcale nie jest przyjazna dla SUCHA.
Weźmy aplikację B, która jest SUCHA, ale z powodu dodatkowych abstrakcji potrzebowała więcej linii.
Zgłoszenie zmiany jest składane, co wymaga zmiany logiki.
W przypadku aplikacji B programista zmienia logikę (unikalną, współdzieloną) zgodnie z żądaniem zmiany.
W przypadku aplikacji A programista musi zmienić wszystkie wystąpienia tej logiki, w których pamięta, że jest ona używana.
Może to prowadzić do ogromnej straty czasu. Nie tylko w fazie rozwoju, ale także podczas polowania i znajdowania błędu. Aplikacja może zacząć zachowywać się niepoprawnie w sposób, którego programiści nie mogą łatwo zrozumieć. Doprowadzi to do długich sesji debugowania.
Zamienność programistów
Deweloper A stworzona aplikacja A. Kod nie jest czysty ani czytelny, ale działa jak urok i działa w produkcji. Nic dziwnego, że nie ma również dokumentacji.
Deweloper A jest nieobecny przez miesiąc z powodu wakacji. Zgłoszono żądanie zmiany awaryjnej. Nie może się doczekać kolejnych trzech tygodni na powrót Dev A.
Deweloper B musi wprowadzić tę zmianę. Teraz musi przeczytać całą bazę kodu, zrozumieć, jak wszystko działa, dlaczego działa i co próbuje osiągnąć. To trwa wieki, ale powiedzmy, że może to zrobić za trzy tygodnie.
W tym samym czasie aplikacja B (utworzona przez dev B) ma awarię. Dev B jest zajęty, ale Dev C jest dostępny, mimo że nie zna bazy kodów. Co robimy?
Dev A wraca z wakacji i widzi, że B nie zrozumiał kodu i dlatego źle go zaimplementował. To nie wina B, ponieważ wykorzystał wszystkie dostępne zasoby, kod źródłowy po prostu nie był wystarczająco czytelny. Czy A musi teraz poświęcać czas na ustalanie czytelności kodu?
Wszystkie te problemy i wiele innych powodują marnowanie czasu . Tak, w krótkim okresie czysty kod wymaga teraz większego wysiłku , ale w przyszłości będzie on wypłacał dywidendy, kiedy trzeba będzie rozwiązać nieuniknione błędy / zmiany.
Kierownictwo musi zrozumieć, że krótkie zadanie pozwoli Ci zaoszczędzić kilka długich zadań w przyszłości. Niezaplanowanie planuje niepowodzenie.
Moje wyjaśnienie polega na pytaniu kierownictwa, co wolą: aplikacji z bazą kodową 100KLOC, którą można opracować za trzy miesiące, lub bazą kodową 50KLOC, którą można opracować za sześć miesięcy.
Oczywiście wybiorą krótszy czas programowania, ponieważ zarząd nie dba o KLOC . Menedżerowie, którzy koncentrują się na KLOC, zarządzają mikrozarządzaniem, a jednocześnie nie są informowani o tym, czym próbują zarządzać.
źródło
Myślę, że powinieneś bardzo uważać na stosowanie praktyk „czystego kodu”, na wypadek gdyby prowadziły one do większej ogólnej złożoności. Przedwczesne refaktoryzowanie jest źródłem wielu złych rzeczy.
Wyodrębnienie warunku do funkcji prowadzi do prostszego kodu w punkcie, z którego warunek został wyodrębniony , ale prowadzi do bardziej ogólnej złożoności, ponieważ masz teraz funkcję, która jest widoczna z większej liczby punktów w programie. Dodajesz niewielką złożoność do wszystkich innych funkcji, w których ta nowa funkcja jest teraz widoczna.
Nie twierdzę, że nie powinieneś wyodrębniać warunku, po prostu powinieneś dokładnie rozważyć, jeśli musisz.
We wszystkich powyższych przypadkach przyczyną wyodrębnienia jest po prostu „czysty kod”. Co więcej, prawdopodobnie nie miałbyś nawet wątpliwości, czy byłoby to właściwe.
W razie wątpliwości zawsze wybieram najprostszy i najprostszy kod.
źródło
Zwracam uwagę, że nie ma w tym nic złego:
Przynajmniej zakładając, że zostanie użyty jeden raz.
Mógłbym z tym bardzo łatwo mieć problemy:
Kilka rzeczy, na które uważam:
Jeśli jest używany raz, jest łatwy do przeanalizowania i zajmuje mniej niż jedną linię, po drugie zgadłbym decyzję. Prawdopodobnie nie zadzwoniłbym, gdyby nie był to szczególny problem zespołu.
Z drugiej strony widziałem, jak metody robią coś takiego:
Ten przykład oczywiście nie jest SUCHY.
Lub nawet ta ostatnia wypowiedź może dać inny przykład:
Celem powinno być zwiększenie czytelności kodu:
Kolejny scenariusz:
Możesz mieć metodę taką jak:
Jeśli jest to zgodne z logiką biznesową i nie jest ponownie wykorzystywane, nie ma problemu.
Ale kiedy ktoś pyta „Dlaczego„ @ ”jest zapisywane, bo to nie w porządku!” i zdecydujesz się dodać rzeczywistą weryfikację, a następnie wypakuj ją!
Będziesz zadowolony, że tak zrobiłeś, gdy będziesz musiał również uwzględnić drugie konto e-mail prezydentów Pr3 $ sid3nt @ h0m3! @ Mydomain.com i zdecydujesz się po prostu wyjść i spróbować wspierać RFC 2822.
Na czytelność:
Jeśli Twój kod jest tak wyraźny, nie potrzebujesz tutaj komentarzy. W rzeczywistości nie potrzebujesz komentarzy, aby powiedzieć, co robi kod przez większość czasu, ale raczej dlaczego :
Niezależnie od tego, czy komentarze powyżej instrukcji if lub wewnątrz małej metody są dla mnie pedantyczne. Mógłbym nawet argumentować przeciwnie do przydatności z dobrymi komentarzami w innej metodzie, ponieważ teraz musiałbyś przejść do innej metody, aby zobaczyć, jak i dlaczego robi to, co robi.
Podsumowując: Nie mierz tych rzeczy; Skoncentruj się na zasadach, z których zbudowano tekst (DRY, SOLID, KISS).
źródło
Whether the comments above an if statement or inside a tiny method is to me, pedantic.
Jest to problem „słomy, która złamała grzbiet wielbłąda”. Masz rację, że ta jedna rzecz nie jest szczególnie trudna do odczytania od razu. Ale jeśli masz duży metody (np duży import), która ma dziesiątki tych małych ocenach, uwzględniając te zamknięte w czytelnych nazw metodą (IsUserActive
,GetAverageIncome
,MustBeDeleted
, ...) stanie się zauważalna poprawa na odczytanie kodu. Problem w tym przykładzie polega na tym, że obserwuje tylko jedną słomkę, a nie cały pakiet, który łamie grzbiet wielbłąda.if (contact.email != null && contact.email.contains('@'))
jest błędne. Jeśli if jest fałszem, żadna z pozostałych linii if nie może być prawdziwa. Nie jest to wcale widoczne wLooksSortaLikeAnEmail
bloku. Funkcja zawierająca pojedynczy wiersz kodu nie jest dużo lepsza niż komentarz wyjaśniający, jak działa ten wiersz.Clean Code to doskonała książka, którą warto przeczytać, ale nie jest to ostateczny autorytet w takich sprawach.
Podział kodu na funkcje logiczne jest zwykle dobrym pomysłem, ale niewielu programistów robi to w takim stopniu, w jakim robi to Martin - w pewnym momencie otrzymujesz malejące zwroty z przekształcania wszystkiego w funkcje i może być trudne do naśladowania, gdy cały kod jest mały sztuk.
Jedną z opcji, gdy nie warto tworzyć zupełnie nowej funkcji, jest po prostu użycie zmiennej pośredniej:
Dzięki temu kod jest łatwy do naśladowania bez konieczności częstego przeskakiwania do pliku.
Innym problemem jest to, że Clean Code staje się już dość stary jak książka. Wiele inżynierii oprogramowania poszło w kierunku programowania funkcjonalnego, podczas gdy Martin robi wszystko, aby dodawać stan do rzeczy i tworzyć obiekty. Podejrzewam, że napisałby zupełnie inną książkę, gdyby napisał ją dzisiaj.
źródło
Biorąc pod uwagę fakt, że warunek „aktualny adres e-mail” akceptuje obecnie bardzo nieprawidłowy adres e-mail „
@
”, uważam, że masz wszelkie powody, by wyodrębnić klasę EmailValidator. Co więcej, użyj dobrej, dobrze przetestowanej biblioteki do sprawdzania poprawności adresów e-mail.Linie kodu jako metryki nie mają znaczenia. Ważnymi pytaniami w inżynierii oprogramowania nie są:
Ważne pytania to:
Nigdy nie zastanawiałem się nad LoC podczas pisania kodu w jakimkolwiek celu oprócz Code Golf. Zadałem sobie pytanie: „Czy mógłbym napisać to bardziej zwięźle?”, Ale dla celów czytelności, łatwości konserwacji i wydajności, a nie tylko długości.
Jasne, może mógłbym użyć długiego łańcucha operacji typu boolean zamiast metody użyteczności, ale czy powinienem?
Twoje pytanie w gruncie rzeczy przypomina mi długie łańcuchy boolanów, które napisałem i zdałem sobie sprawę, że prawdopodobnie powinienem napisać jedną lub więcej metod użyteczności.
źródło
Na jednym poziomie mają rację - mniej kodu jest lepsze. Inną odpowiedzią jest Brama, wolę:
Krótko mówiąc, im mniej masz kodu, tym mniej może się nie udać. Jeśli coś nie jest konieczne, to wytnij.
Jeśli kod jest zbyt skomplikowany, uprość go, aż pozostaną rzeczywiste elementy funkcjonalne.
Ważne jest tutaj to, że wszystkie odnoszą się do funkcjonalności i mają tylko minimum wymagane do tego. Nie mówi nic o tym , jak to jest wyrażone.
To, co robisz, próbując mieć czysty kod, nie jest sprzeczne z powyższym. Dodajesz do LOC, ale nie dodajesz nieużywanej funkcjonalności.
Ostatecznym celem jest posiadanie czytelnego kodu, ale bez zbędnych dodatków. Te dwie zasady nie powinny działać przeciwko sobie.
Metaforą byłoby budowanie samochodu. Funkcjonalną częścią kodu jest podwozie, silnik, koła ... co sprawia, że samochód jeździ. To, jak je rozbijasz, przypomina bardziej zawieszenie, wspomaganie kierownicy itp., Ułatwia obsługę. Chcesz, aby mechanika była tak prosta, jak to tylko możliwe, jednocześnie wykonując swoją pracę, aby zminimalizować ryzyko, że coś pójdzie nie tak, ale to nie przeszkadza ci mieć przyjemnych miejsc.
źródło
Istnieje wiele mądrości w istniejących odpowiedziach, ale chciałbym dodać jeszcze jeden czynnik: język .
Niektóre języki wymagają więcej kodu niż inne, aby uzyskać ten sam efekt. W szczególności, podczas gdy Java (która, jak podejrzewam, jest językiem tego pytania), jest niezwykle dobrze znana i ogólnie bardzo solidna, przejrzysta i prosta, niektóre bardziej nowoczesne języki są o wiele bardziej zwięzłe i wyraziste.
Na przykład w Javie napisanie nowej klasy z trzema właściwościami, z których każda zawiera getter i setter, oraz jednego lub więcej konstruktorów może zająć 50 wierszy, podczas gdy możesz osiągnąć dokładnie to samo w jednym wierszu Kotlina * lub Scali. (Jeszcze większa oszczędność jeśli chciał również odpowiednie
equals()
,hashCode()
itoString()
metod).Rezultat jest taki, że w Javie dodatkowa praca oznacza większe prawdopodobieństwo ponownego użycia ogólnego obiektu, który tak naprawdę nie pasuje, do ściśnięcia właściwości w istniejących obiektach lub do przekazania pojedynczej wiązki „nagich” właściwości indywidualnie; w zwięzłym, ekspresyjnym języku bardziej prawdopodobne jest, że napiszesz lepszy kod.
(Podkreśla to różnicę między złożonością „powierzchniową” kodu, a złożonością pomysłów / modeli / przetwarzania, które implementuje. Linie kodu nie są złą miarą pierwszego, ale mają znacznie mniej wspólnego z drugim .)
Tak więc „koszt” właściwego wykonania zależy od języka. Być może jednym ze znaków dobrego języka jest taki, który nie każe wybierać między robieniem rzeczy dobrze a robieniem ich po prostu!
(* To nie jest tak naprawdę miejsce na wtyczkę, ale Kotlin jest warty obejrzenia IMHO.)
źródło
Załóżmy, że obecnie pracujesz z klasą
Contact
. Fakt, że piszesz inną metodę weryfikacji adresu e-mail, świadczy o tym, że klasaContact
nie ponosi żadnej odpowiedzialności.Zajmuje się również pewną odpowiedzialnością za wiadomości e-mail, co idealnie byłoby własną klasą.
Kolejnym dowodem na to, że Twój kod jest połączeniem
Contact
iEmail
klasą, jest to, że nie będziesz w stanie łatwo przetestować kodu weryfikacyjnego e-mail. Wymaganie wielu manewrów, aby dotrzeć do kodu sprawdzającego pocztę e-mail w dużej metodzie z właściwymi wartościami. Zobacz metodę viz poniżej.Z drugiej strony, jeśli miałeś osobną klasę e-mail z metodą sprawdzania poprawności wiadomości e-mail, to aby przeprowadzić test jednostkowy kodu weryfikacyjnego, wystarczy wykonać jedno proste połączenie
Email.Validation()
z danymi testowymi.Treść dodatkowa: rozmowa MFeather na temat głębokiej synergii między testowalnością a dobrym projektem.
źródło
Stwierdzono, że zmniejszenie LOC jest skorelowane ze zmniejszonymi wadami, niczym więcej. Zakładając, że ilekroć zmniejszysz LOC, zmniejszysz prawdopodobieństwo wystąpienia defektów, wpadając w pułapkę przekonania, że korelacja równa się przyczynowości. Zmniejszony LOC jest wynikiem dobrych praktyk programistycznych, a nie tego, co czyni kod dobrym.
Z mojego doświadczenia wynika, że ludzie, którzy potrafią rozwiązać problem z mniejszym kodem (na poziomie makro), zwykle są bardziej wykwalifikowani niż ci, którzy piszą więcej kodu, aby zrobić to samo. To, co ci wykwalifikowani programiści robią, aby zmniejszyć liczbę wierszy kodu, to wykorzystanie / tworzenie abstrakcji i rozwiązań wielokrotnego użytku w celu rozwiązania typowych problemów. Nie spędzają czasu na liczeniu linii kodu i zadręczaniu się, czy mogą przeciąć linię tu czy tam. Często kod, który piszą, jest bardziej szczegółowy, niż to konieczne, po prostu piszą mniej.
Dam ci przykład. Musiałem poradzić sobie z logiką wokół przedziałów czasowych i tego, jak się pokrywają, czy sąsiadują ze sobą i jakie luki istnieją między nimi. Kiedy zacząłem pracować nad tymi problemami, miałem bloki kodu wykonujące obliczenia wszędzie. W końcu zbudowałem klasy reprezentujące przedziały czasowe i operacje, które obliczały nakładanie się, uzupełnienia itp. To natychmiast usunęło duże obszary kodu i przekształciło je w kilka wywołań metod. Ale same te klasy w ogóle nie były pisane zwięźle.
Mówiąc wprost: jeśli próbujesz zmniejszyć LOC, próbując wyciąć wiersz kodu tu lub tam z większą liczbą krótkich, robisz to źle. To tak, jakby próbować schudnąć, zmniejszając ilość spożywanych warzyw. Napisz kod, który jest łatwy do zrozumienia, utrzymania i debugowania oraz ograniczenia LOC poprzez ponowne użycie i abstrakcję.
źródło
Zidentyfikowałeś ważny kompromis
Zatem rzeczywiście istnieje tutaj kompromis i jest on nieodłączny od abstrakcji jako całości. Ilekroć ktoś próbuje wciągnąć N linii kodu do swojej funkcji w celu nazwania go i odizolowania, jednocześnie ułatwia czytanie strony wywołującej (odwołując się do nazwy, a nie do wszystkich krwawych szczegółów leżących u podstaw tej nazwy) i bardziej złożone (teraz masz znaczenie splątane w dwóch różnych częściach bazy kodu). „Łatwy” jest przeciwieństwem „twardego”, ale nie jest synonimem „prostego”, który jest przeciwieństwem „złożonego”. Obie nie są przeciwieństwami, a abstrakcja zawsze zwiększa złożoność w celu wstawienia jakiejś formy z łatwością.
Widzimy dodatkową złożoność bezpośrednio, gdy jakaś zmiana wymagań biznesowych powoduje, że abstrakcja zaczyna wyciekać. Być może jakaś nowa logika poszedłaby najbardziej naturalnie w środek wstępnie wyodrębnionego kodu, na przykład, jeśli wyodrębniony kod przechodzi przez jakieś drzewo i naprawdę chciałbyś zbierać (i być może działać) jakieś informacje, gdy jesteś przemierzając drzewo. W międzyczasie, jeśli wyodrębniłeś ten kod, mogą istnieć inne strony wywołujące, a dodanie wymaganej logiki w środku metody może uszkodzić te inne strony wywoływania. Zobacz, ilekroć zmieniamy wiersz kodu, musimy tylko spojrzeć na jego bezpośredni kontekst; kiedy zmieniamy metodę, musimy Cmd-F cały nasz kod źródłowy szukać wszystkiego, co może się zepsuć w wyniku zmiany kontraktu tej metody,
W takich przypadkach chciwy algorytm może zawieść
Złożoność sprawiła również, że kod jest w pewnym sensie mniej czytelny niż bardziej. W poprzednim zadaniu miałem do czynienia z interfejsem API HTTP, który był bardzo starannie i precyzyjnie podzielony na kilka warstw, każdy punkt końcowy jest określany przez kontroler, który sprawdza poprawność kształtu wiadomości przychodzącej, a następnie przekazuje ją menedżerowi „warstwy logiki biznesowej” , który następnie zwrócił się z prośbą o „warstwę danych”, która była odpowiedzialna za kilka zapytań do warstwy „obiektu dostępu do danych”, która była odpowiedzialna za utworzenie kilku Delegatów SQL, którzy faktycznie odpowiedzieliby na twoje pytanie. Pierwszą rzeczą, jaką mogę o tym powiedzieć, było to, że około 90% kodu to płyta kopiująca i wklejająca, innymi słowy, nie było żadnych operacji. Tak więc w wielu przypadkach odczytanie dowolnego fragmentu kodu było bardzo „łatwe”, ponieważ „och ten menedżer właśnie przekazuje żądanie do tego obiektu dostępu do danych”.dużo przełączania kontekstu i znajdowania plików oraz próby śledzenia informacji, których nigdy nie powinieneś śledzić, „na tej warstwie nazywa się X, na tej drugiej warstwie nazywa się X”, a na drugiej inna warstwa ”.
Myślę, że kiedy skończyłem, ten prosty interfejs CRUD API był na etapie, w którym wydrukowanie go przy 30 liniach na stronie zajęłoby 10-20 pięćset stron podręczników na półce: była to cała encyklopedia powtarzalnych kod. Jeśli chodzi o zasadniczą złożoność, nie jestem pewien, czy była tam nawet połowa podręcznika o zasadniczej złożoności; mieliśmy do dyspozycji tylko 5-6 diagramów baz danych. Dokonanie jakiejkolwiek drobnej zmiany było gigantycznym przedsięwzięciem, uczenie się, że to gigantyczne przedsięwzięcie, dodanie nowej funkcjonalności stało się tak bolesne, że faktycznie mieliśmy pliki szablonów, których użylibyśmy do dodania nowej funkcjonalności.
Widziałem więc z pierwszej ręki, jak uczynienie każdej części bardzo czytelną i oczywistą może sprawić, że całość będzie bardzo nieczytelna i nieoczywista. Oznacza to, że chciwy algorytm może zawieść. Znasz chciwy algorytm, tak? „Zamierzam zrobić wszystko, by lokalny krok najbardziej poprawił sytuację, a potem będę mieć pewność, że znajdę się w sytuacji globalnej poprawy”. Jest to często piękna pierwsza próba, ale można ją również pominąć w skomplikowanym kontekście. Na przykład w produkcji możesz spróbować zwiększyć wydajność każdego poszczególnego etapu złożonego procesu produkcyjnego - rób większe partie, krzycz na ludzi na podłodze, którzy wydają się nic nie robić, aby zająć się czymś innym - i może to często zniszczyć globalną wydajność systemu.
Najlepsza praktyka: nawiąż połączenie używając SUCHOŚCI i długości
(Uwaga: ten tytuł sekcji jest trochę żartem; często mówię moim znajomym, że gdy ktoś mówi „powinniśmy zrobić X, bo tak mówią najlepsze praktyki ”, to w 90% przypadków nie mówią o czymś takim jak wstrzyknięcie SQL lub mieszanie hasła czy cokolwiek innego - jednostronne najlepsze praktyki - dlatego stwierdzenie to można przełożyć w 90% przypadków na „powinniśmy zrobić X, bo tak mówię ”. Na przykład mogą mieć artykuł na blogu z jakiegoś biznesu, który spisał się lepiej z X zamiast X ', ale generalnie nie ma gwarancji, że Twoja firma jest podobna do tej firmy, i ogólnie jest jakiś inny artykuł z innej firmy, który lepiej sobie radził z X' niż X. Więc nie bierz tytułu poważnie.)
To, co poleciłbym, opiera się na wykładzie Jacka Diedericha pt. Stop Writing Classes (youtube.com) . Mówi w tym przemówieniu o kilku ważnych kwestiach: na przykład, że możesz wiedzieć, że klasa jest tak naprawdę tylko funkcją, gdy ma tylko dwie publiczne metody, a jedną z nich jest konstruktor / inicjator. Ale w jednym przypadku mówi o tym, w jaki sposób hipotetyczna biblioteka, którą zastąpił ciągiem mowy, gdy „Muffin” zadeklarowała własną klasę „MuffinHash”, która była podklasą wbudowanego
dict
typu, który ma Python. Implementacja była całkowicie pusta - ktoś pomyślał: „być może będziemy musieli później dodać niestandardowe funkcje do słowników Pythona, na wszelki wypadek wprowadzimy abstrakcję”.A jego wyzywająca odpowiedź brzmiała po prostu: „zawsze możemy to zrobić później, jeśli zajdzie taka potrzeba”.
Myślę, że czasami udajemy, że w przyszłości będziemy gorszymi programistami niż obecnie, więc możemy chcieć wstawić coś małego, co może nas uszczęśliwić w przyszłości. Przewidujemy przyszłe potrzeby. „Jeśli ruch jest 100 razy większy, niż się spodziewamy, takie podejście nie będzie się skalować, dlatego musimy włożyć w to trudniejsze podejście, które będzie skalowane”. Bardzo podejrzane.
Jeśli poważnie podchodzimy do tych rad, musimy ustalić, kiedy nadejdzie „później”. Prawdopodobnie najbardziej oczywistą rzeczą byłoby ustalenie górnej granicy długości rzeczy ze względów stylowych. I myślę, że najlepszą pozostałą radą byłoby użycie OSUSZANIA - nie powtarzaj się - z tymi heurystykami dotyczącymi długości linii, aby załatać dziurę w zasadach SOLID. Opierając się na heurystyce 30 wierszy będących „stroną” tekstu i analogią do prozy,
Jak już wspomniałem, przetestowałem te statystyki na bieżącym drzewie źródeł Linuksa, aby znaleźć te przybliżone wartości procentowe, ale są one również w pewnym sensie uzasadnione w literackiej analogii.
źródło