Ostatnio zmieniłem kod w pracy i pomyślałem, że wykonałem dobrą robotę. Zrzuciłem 980 linii kodu do 450 i zmniejszyłem o połowę liczbę klas.
Pokazując to moim kolegom, niektórzy nie zgodzili się, że to poprawa.
Powiedzieli - „mniej linii kodu niekoniecznie jest lepsze”
Widzę, że mogą wystąpić ekstremalne przypadki, w których ludzie piszą naprawdę długie linie i / lub umieszczają wszystko w jednej metodzie, aby zapisać kilka linii, ale nie to zrobiłem. Moim zdaniem kod jest dobrze skonstruowany i łatwiejszy do zrozumienia / utrzymania, ponieważ jest o połowę mniejszy.
Próbuję zrozumieć, dlaczego ktokolwiek chciałby pracować z podwójnym kodem wymaganym do wykonania pracy, i zastanawiam się, czy ktoś czuje się tak samo jak moi koledzy i może zrobić dobre przypadki, aby mieć więcej kodu niż mniej ?
źródło
Odpowiedzi:
Chuda osoba niekoniecznie jest zdrowsza niż osoba z nadwagą.
980-liniowa historia dla dzieci jest łatwiejsza do odczytania niż 450-tezowa fizyka.
Istnieje wiele atrybutów, które określają jakość kodu. Niektóre są po prostu obliczane, na przykład złożoność cykliczna i złożoność Halsteada . Inne są bardziej luźno zdefiniowane, takie jak spójność , czytelność, zrozumiałość, rozszerzalność, niezawodność, poprawność, samok dokumentacja, czystość, testowalność i wiele innych.
Możliwe, że na przykład zmniejszyłeś całkowitą długość kodu - wprowadziłeś dodatkową nieuzasadnioną złożoność i uczyniłeś kod bardziej tajemniczym.
Podział długiego kodu na małe metody może być tak samo szkodliwy, jak i korzystny .
Poproś współpracowników o udzielenie konkretnej informacji zwrotnej na temat tego, dlaczego uważają, że Twoje działania refaktoryzacyjne przyniosły niepożądany rezultat.
źródło
Co ciekawe, ja i kolega jesteśmy obecnie w środku refaktora, który zwiększy liczbę klas i funkcji o nieco mniej niż dwukrotnie, chociaż linie kodu pozostaną takie same. Tak się składa, że mam dobry przykład.
W naszym przypadku mieliśmy jedną warstwę abstrakcji, która naprawdę powinna być dwie. Wszystko zostało wciśnięte w warstwę interfejsu użytkownika. Dzięki podzieleniu go na dwie warstwy wszystko staje się bardziej spójne, a testowanie i konserwacja poszczególnych elementów staje się znacznie prostsza.
To nie rozmiar kodu przeszkadza kolegom, to coś innego. Jeśli nie potrafią go sformułować, spróbuj spojrzeć na kod tak, jakbyś nigdy nie widział starej implementacji, i oceń go na podstawie własnych zalet, a nie tylko w porównaniu. Czasami, gdy robię długi refaktor, tracę z oczu pierwotny cel i posuwam się za daleko. Przyjrzyj się krytycznie „dużemu obrazowi” i przywróć go na właściwe tory, być może z pomocą programisty parowego, którego porady cenisz.
źródło
Przychodzi mi na myśl cytat, często przypisywany Albertowi Einsteinowi:
Jeśli przesadzisz, przycinając rzeczy, może to utrudnić odczytanie kodu. Ponieważ „łatwy / trudny do odczytania” może być bardzo subiektywnym terminem, wyjaśnię dokładnie, co rozumiem przez to: miarę stopnia trudności, jaką będzie miał wykwalifikowany programista w określaniu „co robi ten kod?”. po prostu patrząc na źródło, bez pomocy specjalistycznych narzędzi.
Języki takie jak Java i Pascal są niesławne ze względu na swoją gadatliwość. Ludzie często wskazują na pewne elementy składniowe i szyderczo mówią, że „są po to, aby ułatwić kompilatorowi pracę”. Jest to mniej więcej prawda, z wyjątkiem części „sprawiedliwej”. Im bardziej wyraźne są informacje, tym kod jest łatwiejszy do odczytania i zrozumienia, nie tylko przez kompilator, ale także przez człowieka.
Jeśli powiem
var x = 2 + 2;
, to od razu oczywiste, żex
ma to być liczba całkowita. Ale jeśli powiemvar foo = value.Response;
, o wiele mniej jasne jest to, cofoo
reprezentuje lub jakie są jego właściwości i możliwości. Nawet jeśli kompilator może to łatwo wywnioskować, nakłada na osobę znacznie więcej wysiłku poznawczego.Pamiętaj, że programy muszą być napisane, aby ludzie mogli je czytać, a tylko przypadkowo, aby maszyny mogły je uruchomić. (Jak na ironię, ten cytat pochodzi z podręcznika poświęconego niesławnemu językowi, ponieważ jest niezwykle trudny do odczytania!) Dobrym pomysłem jest usunięcie rzeczy, które są zbędne, ale nie zabieraj kodu, który ułatwi innym ludziom dowiedzieć się, co się dzieje, nawet jeśli nie jest to absolutnie konieczne do napisania programu.
źródło
var
przykład nie jest szczególnie dobry uproszczenia, ponieważ większość czasu na czytanie i zrozumienie kodu polega na zastanawianie się, zachowania na pewnym poziomie abstrakcji, więc znając rzeczywiste typy konkretnych zmiennych zwykle niczego nie zmienia (tylko to pomaga zrozumieć niższe abstrakcje). Lepszym przykładem może być wiele wierszy prostego kodu zmiażdżonych w jedną zwiniętą instrukcję - np.if ((x = Foo()) != (y = Bar()) && CheckResult(x, y))
Potrzeba czasu na grok, a znajomość typówx
luby
wcale nie pomaga.Dłuższy kod może być łatwiejszy do odczytania. Zwykle jest odwrotnie, ale istnieje wiele wyjątków - niektóre z nich zostały przedstawione w innych odpowiedziach.
Ale spójrzmy z innej perspektywy. Zakładamy, że nowy kod będzie postrzegany jako lepszy przez większość wykwalifikowanych programistów, którzy widzą 2 fragmenty kodu bez dodatkowej wiedzy o kulturze firmy, podstawie kodu lub mapie drogowej. Nawet wtedy istnieje wiele powodów, by sprzeciwić się nowemu kodowi. Dla zwięzłości zadzwonię do „Ludzi krytykujących nowy kod” Pecritenc :
źródło
Jaki rodzaj kodu jest lepszy, może zależeć od wiedzy programistów i narzędzi, których używają. Na przykład oto, dlaczego to, co normalnie byłoby uważane za źle napisany kod, może być bardziej skuteczne w niektórych sytuacjach niż dobrze napisany kod obiektowy, który w pełni wykorzystuje dziedziczenie:
(1) Niektórzy programiści po prostu nie mają intuicyjnej wiedzy na temat programowania obiektowego. Jeśli twoją metaforą projektu oprogramowania jest obwód elektryczny, wówczas będziesz się spodziewać dużo duplikacji kodu. Będziesz chciał zobaczyć mniej więcej te same metody w wielu klasach. Sprawią, że poczujesz się jak w domu. A projekt, w którym musisz szukać metod w klasach rodzicielskich, a nawet w klasach dziadków, aby zobaczyć, co się dzieje, może wydawać się wrogi. Nie chcesz zrozumieć, jak działa klasa nadrzędna, a następnie zrozumieć, czym różni się bieżąca klasa. Chcesz bezpośrednio zrozumieć, jak działa bieżąca klasa, a fakt, że informacje są rozłożone na kilka plików, jest mylący.
Ponadto, gdy chcesz po prostu naprawić konkretny problem w określonej klasie, możesz nie chcieć zastanawiać się, czy rozwiązać problem bezpośrednio w klasie podstawowej, czy też zastąpić metodę z bieżącej klasy zainteresowań. (Bez dziedziczenia nie musiałbyś podejmować świadomej decyzji. Domyślnie po prostu ignorujesz podobne problemy w podobnych klasach, dopóki nie zostaną zgłoszone jako błędy). Ten ostatni aspekt nie jest tak naprawdę uzasadnionym argumentem, chociaż może wyjaśniać niektóre z sprzeciw.
(2) Niektórzy programiści często używają debuggera. Mimo że ogólnie jestem zdecydowanie zwolennikiem dziedziczenia kodu i zapobiegania duplikowaniu, dzielę się frustracją opisaną w (1) podczas debugowania kodu zorientowanego obiektowo. Podczas wykonywania kodu czasami przeskakuje on między klasami (przodkami), nawet jeśli pozostaje w tym samym obiekcie. Ponadto, ustawiając punkt przerwania w dobrze napisanym kodzie, jest bardziej prawdopodobne, że zostanie on wyzwolony, gdy nie będzie to pomocne, więc może być konieczne poświęcenie wysiłku na uzależnienie go (tam, gdzie jest to praktyczne) lub nawet na ręczne kontynuowanie wiele razy przed odpowiednim wyzwalaczem.
źródło
To całkowicie zależy. Pracowałem nad projektem, który nie dopuszcza zmiennych logicznych jako parametrów funkcji, ale zamiast tego wymaga dedykowania
enum
dla każdej opcji.Więc,
jest dużo bardziej gadatliwy niż
Jednak,
jest o wiele bardziej czytelny niż
Kompilator powinien wygenerować ten sam kod dla obu, więc nie można nic zyskać, używając krótszej formy.
źródło
Powiedziałbym, że spójność może stanowić problem.
Na przykład w aplikacji internetowej Załóżmy, że masz stronę administratora, na której indeksujesz wszystkie produkty, czyli zasadniczo ten sam kod (indeks), którego używałbyś w sytuacji na stronie głównej, aby po prostu zindeksować produkty.
Jeśli zdecydujesz się podzielić na części wszystko, abyś mógł pozostać SUCHY i elegancki, musisz dodać wiele warunków dotyczących tego, czy użytkownik przeglądający jest administratorem, czy nie, i zaśmiecać kod niepotrzebnymi rzeczami, co spowoduje, że będzie on bardzo nieczytelny projektant!
Więc w takiej sytuacji, nawet jeśli kod jest prawie taki sam, tylko dlatego, że można go skalować do czegoś innego, a przypadki użycia mogą się nieznacznie zmienić, źle byłoby pójść za każdym z nich, dodając warunki i ifs. Tak więc dobrą strategią byłoby porzucenie koncepcji DRY i rozbicie kodu na części, które można konserwować.
źródło
Kiedy mniej kodu jest mniej konserwowalne / rozszerzalne niż więcej kodu. Refaktoryzacja pod kątem zwięzłości kodu często usuwa „niepotrzebne” konstrukcje kodu w interesie LoC. Problem w tym, że takie konstrukcje kodu, jak deklaracje interfejsu równoległego, wyodrębnione metody / podklasy itp. Są konieczne, jeśli ten kod kiedykolwiek będzie musiał zrobić więcej niż obecnie, lub zrobić to inaczej. W skrajności niektóre rozwiązania dostosowane do konkretnego problemu mogą w ogóle nie działać, jeśli definicja problemu zmieni się tylko trochę.
Jeden przykład; masz listę liczb całkowitych. Każda z tych liczb całkowitych ma zduplikowaną wartość na liście, z wyjątkiem jednej. Twój algorytm musi znaleźć tę niesparowaną wartość. Ogólnym rozwiązaniem jest porównanie każdej liczby z każdą inną liczbą, dopóki nie znajdziesz liczby, która nie ma duplikatu na liście, co jest operacją N ^ 2-krotną. Możesz również zbudować histogram za pomocą tablicy mieszającej, ale jest to bardzo mało miejsca. Można jednak ustawić czas liniowy i stałą przestrzeń za pomocą bitowej operacji XOR; XOR każdą liczbą całkowitą względem działającej „sumy” (zaczynając od zera), a na końcu suma bieżąca będzie wartością twojej niesparowanej liczby całkowitej. Bardzo elegancko. Dopóki wymagania się nie zmienią, więcej niż jedna liczba na liście może zostać sparowana lub liczby całkowite zawierają zero. Teraz twój program zwraca śmieci lub niejednoznaczne wyniki (jeśli zwraca zero, czy to oznacza, że wszystkie elementy są sparowane, czy też niesparowany element ma zero?). Taki jest problem „sprytnych” wdrożeń w programowaniu w świecie rzeczywistym.
źródło
Kod komputerowy musi robić wiele rzeczy. „Minimalistyczny” kod, który nie robi tych rzeczy, nie jest dobrym kodem.
Na przykład program komputerowy powinien obejmować wszystkie możliwe (lub, co najmniej, wszystkie prawdopodobne przypadki). Jeśli fragment kodu obejmuje tylko jeden „przypadek podstawowy” i ignoruje inne, nie jest to dobry kod, nawet jeśli jest krótki.
Kod komputerowy powinien być „skalowalny”. Kod tajemniczy może działać tylko dla jednej specjalistycznej aplikacji, a dłuższy, ale bardziej otwarty program może ułatwić dodawanie nowych aplikacji.
Kod komputerowy powinien być wyraźny. Jak wykazał inny program odpowiadający, możliwe jest, aby koder hard-core mógł wygenerować jedno-liniową funkcję typu „algorytmicznego”, która wykonuje zadanie. Ale jednolinijka musiała zostać podzielona na pięć różnych „zdań”, zanim stanie się jasne dla przeciętnego programisty.
źródło
Wydajność obliczeniowa. Podczas optymalizacji podszewki rur lub równoległych części kodu może być korzystne na przykład nie zapętlanie od 1 do 400, ale od 1 do 50 i umieszczanie 8 wystąpień podobnego kodu w każdej pętli. Nie zakładam, że tak było w twojej sytuacji, ale jest to przykład, w którym więcej linii jest lepszych (pod względem wydajności).
źródło