Jestem inżynierem elektrykiem i nie wiem, co do diabła robię. Proszę zapisać przyszłych opiekunów mojego kodu.
Ostatnio pracowałem nad kilkoma mniejszymi programami (w języku C #), których funkcjonalność jest logicznie „proceduralna”. Na przykład jednym z nich jest program, który zbiera informacje z różnych baz danych, wykorzystuje te informacje do generowania pewnego rodzaju strony podsumowania, drukuje ją, a następnie kończy działanie.
Logika wymagana do tego wszystkiego to około 2000 linii. I na pewno nie chcą rzeczy wszystko to w jednym main()
, a następnie „posprzątać” ze #region
s jak niektóre wcześniejsze deweloperzy robią (dreszcze).
Oto kilka rzeczy, które próbowałem już bez dużej satysfakcji:
Twórz statyczne narzędzia dla każdego zgrubnego elementu funkcjonalności, np. DatabaseInfoGetter, SummaryPageGenerator i PrintUtility. Sprawienie, aby główna funkcja wyglądała następująco:
int main()
{
var thisThing = DatabaseInfoGetter.GetThis();
var thatThing = DatabaseInfoGetter.GetThat();
var summary = SummaryPageGenerator.GeneratePage(thisThing, thatThing);
PrintUtility.Print(summary);
}
W przypadku jednego programu korzystałem nawet z interfejsów
int main()
{
/* pardon the psuedocode */
List<Function> toDoList = new List<Function>();
toDoList.Add(new DatabaseInfoGetter(serverUrl));
toDoList.Add(new SummaryPageGenerator());
toDoList.Add(new PrintUtility());
foreach (Function f in toDoList)
f.Do();
}
Nic z tego nie wydaje się właściwe. Gdy kod się wydłuża, oba te podejścia stają się brzydkie.
Jaki jest dobry sposób na uporządkowanie takich rzeczy?
Odpowiedzi:
Ponieważ nie jesteś profesjonalnym programistą, polecam trzymać się prostoty. Dużo łatwiej będzie programiście wziąć modułowy, proceduralny kod i sprawić, że będzie on OO później, niż naprawi źle napisany program OO. Jeśli nie masz doświadczenia, możesz stworzyć programy OO, które mogą przekształcić się w bezbożny bałagan, który ani ci nie pomoże, ani nikomu, kto przyjdzie po ciebie.
Myślę, że twój pierwszy instynkt, kod „ta rzecz-ta rzecz” w pierwszym przykładzie jest właściwą ścieżką. To, co chcesz zrobić, jest jasne i oczywiste. Nie przejmuj się zbytnio wydajnością kodu, przejrzystość jest o wiele ważniejsza.
Jeśli segment kodu jest zbyt długi, podziel go na kawałki wielkości kęsa, z których każdy ma swoją funkcję. Jeśli jest za krótki, rozważ użycie mniejszej liczby modułów i ustawienie większej liczby w szeregu.
---- Postscript: OO Design Traps
Skuteczna praca z programowaniem OO może być trudna. Są nawet ludzie, którzy uważają cały model za wadliwy. Jest bardzo dobra książka, której użyłem podczas pierwszej nauki programowania OO, o nazwie Myślenie w Javie (obecnie w czwartym wydaniu). Ten sam autor ma odpowiednią książkę dla C ++. W rzeczywistości programiści mają inne pytanie dotyczące typowych problemów w programowaniu obiektowym .
Niektóre pułapki są wyrafinowane, ale istnieje wiele sposobów na stworzenie problemów w bardzo prosty sposób. Na przykład kilka lat temu w mojej firmie był stażysta, który napisał pierwszą wersję odziedziczonego przeze mnie oprogramowania i stworzył interfejsy do wszystkiego, co mogłobypewnego dnia mają wiele implementacji. Oczywiście w 98% przypadków była tylko jedna implementacja, więc kod został załadowany nieużywanymi interfejsami, co sprawiło, że debugowanie było bardzo irytujące, ponieważ nie można cofnąć się przez wywołanie interfejsu, więc trzeba zrobić wyszukiwanie tekstowe dla implementacji (chociaż teraz używam IntelliJ był wyposażony w funkcję „Pokaż wszystkie implementacje”, ale w tamtych czasach tego nie miałem). Zasada jest taka sama, jak w przypadku programowania proceduralnego: zawsze koduj jedną rzecz. Tylko wtedy, gdy masz dwie lub więcej rzeczy, utwórz abstrakcję.
Podobny rodzaj błędów projektowych można znaleźć w interfejsie API Java Swing. Używają modelu publikuj-subskrybuj dla systemu menu Swing. To sprawia, że tworzenie i debugowanie menu w Swing to kompletny koszmar. Ironia polega na tym, że jest to całkowicie bezcelowe. Praktycznie nigdy nie ma sytuacji, w której wiele funkcji musiałoby „subskrybować” kliknięcie menu. Również publikacja-subskrypcja była całkowitym brakiem ognia, ponieważ system menu jest zwykle w użyciu. To nie jest tak, że funkcje subskrybują, a następnie wypisują się losowo. Fakt, że „profesjonalni” programiści w firmie Sun popełnili taki błąd, pokazuje, jak łatwo nawet profesjonalistom zrobić monumentalne błędy w projekcie OO.
Jestem bardzo doświadczonym programistą z wieloletnim doświadczeniem w programowaniu OO, ale nawet jako pierwszy przyznałbym, że jest mnóstwo tonów, których nie znam, a nawet teraz jestem bardzo ostrożny w używaniu wielu OO. Słuchałem długich wykładów współpracownika, który był gorliwym fanem OO, na temat tego, jak robić konkretne projekty. Naprawdę wiedział, co robi, ale szczerze mówiąc, miałem trudności ze zrozumieniem jego programów, ponieważ miały one tak wyrafinowane modele projektowe.
źródło
Pierwsze podejście jest w porządku. W językach proceduralnych, takich jak C, standardowym podejściem jest dzielenie funkcjonalności na przestrzenie nazw - używanie klas statycznych jak
DatabaseInfoGetter
to w zasadzie to samo. Oczywiście takie podejście prowadzi do prostszego, bardziej modułowego i bardziej czytelnego / możliwego do utrzymania kodu niż pchanie wszystkiego w jedną klasę, nawet jeśli wszystko jest podzielone na metody.Mówiąc o tym, spróbuj ograniczyć metody do wykonywania jak najmniejszej liczby akcji. Niektórzy programiści wolą nieco mniejszą szczegółowość, ale ogromne metody są zawsze uważane za szkodliwe.
Jeśli nadal masz problemy ze złożonością, najprawdopodobniej musisz jeszcze bardziej rozbić swój program. Stwórz więcej poziomów w hierarchii - być może
DatabaseInfoGetter
trzeba będzie odwoływać się do innych klas, takich jakProductTableEntry
coś. Piszecie kod proceduralny, ale pamiętajcie, używacie C #, a OOP daje mnóstwo narzędzi w celu zmniejszenia złożoności, np .:Uważaj także na klasy statyczne. Klasy baz danych są dobrymi kandydatami .. tak długo, jak zwykle masz jedną bazę danych, masz pomysł.
Ostatecznie, jeśli myślisz o funkcjach matematycznych i C # nie pasuje do twojego stylu, wypróbuj coś takiego jak Scala, Haskell itp. Są też fajne.
źródło
Odpowiadam 4 lata później, ale kiedy próbujesz robić czynności proceduralne w języku OO, pracujesz nad antypatternem. To nie jest coś, co powinieneś zrobić! Znalazłem blog, który omawia ten antypattern i pokazuje jego rozwiązanie, ale wymaga to dużo refaktoryzacji i zmiany sposobu myślenia. Aby uzyskać więcej informacji, zobacz Kod proceduralny w kodzie obiektowym .
Jak wspomniałeś „to” i „tamto”, zasadniczo sugerujesz, że masz dwie różne klasy. Klasa A (zła nazwa!) I klasa That. Możesz np. Przetłumaczyć to:
zaangażowany w to:
Teraz interesujące byłoby zobaczenie ponad 2000 linii kodu proceduralnego i ustalenie, w jaki sposób różne części mogą być grupowane w osobne klasy i przenosić różne procedury do tych klas.
Każda klasa powinna być prosta i skoncentrowana na robieniu tylko jednej rzeczy. I wydaje mi się, że niektóre części kodu dotyczą już nadklas, które w rzeczywistości są zbyt duże, aby były przydatne. Na przykład DatabaseInfoGetter wydaje się mylący. Co to w ogóle robi? Brzmi zbyt ogólnie. Jednak SummaryPageGenerator jest strasznie specyficzny! A PrintUtility znów jest ogólna.
Na początek powinieneś unikać nazw ogólnych dla swoich klas i zamiast tego zacząć używać bardziej szczegółowych nazw. Na przykład klasa PrintUtility byłaby bardziej klasą Print z klasą Header, klasą Footer, klasą TextBlock, klasą Image i ewentualnie sposobem wskazania, w jaki sposób przebiegałyby w samym wydruku. Jednak przestrzeń nazw PrintUtility .
W przypadku bazy danych podobna historia. Nawet jeśli używasz Entity Framework do uzyskiwania dostępu do bazy danych, powinieneś go ograniczyć do rzeczy, których używasz, i podzielić go na logiczne grupy.
Ale zastanawiam się, czy nadal masz do czynienia z tym problemem po 4 latach. Jeśli tak, to sposób myślenia należy zmienić z „koncepcji proceduralnej” na „koncepcję obiektową”. Co jest trudne, jeśli nie masz doświadczenia ...
źródło
Moim zdaniem powinieneś zmienić swój kod, aby był prosty, spójny i czytelny.
Napisałem kilka postów na ten temat i wierzcie mi, że łatwo je zrozumieć: co to jest dobry kod
Prostota: tak jak płytka drukowana zawiera różne elementy, z których każdy ma obowiązek. Kod powinien być podzielony na mniejsze, prostsze części.
Spójny: używaj wzorów, standardu nazewnictwa elementów i przedmiotów. Lubisz narzędzia, które działają tak samo przez cały czas i chcesz wiedzieć, gdzie je znaleźć. Podobnie jest z kodem.
Czytelny: nie używaj akronimów, chyba że są one powszechnie używane i znane w dziedzinie, na którą program jest kierowany (mattnz), wolą wymawiane nazwy, które są jasne i łatwe do zrozumienia.
źródło