Obecnie pracuję nad zestawem raportów, które zawierają wiele różnych sekcji (wszystkie wymagają innego formatowania) i próbuję znaleźć najlepszy sposób na ustrukturyzowanie mojego kodu. Podobne raporty, które zrobiliśmy w przeszłości, mają bardzo duże (ponad 200 linii) funkcje, które wykonują wszystkie operacje na danych i formatują raport, dzięki czemu przepływ pracy wygląda mniej więcej tak:
DataTable reportTable = new DataTable();
void RunReport()
{
reportTable = DataClass.getReportData();
largeReportProcessingFunction();
outputReportToUser();
}
Chciałbym móc podzielić te duże funkcje na mniejsze części, ale obawiam się, że skończę z dziesiątkami funkcji, których nie można ponownie użyć, i podobną funkcją „rób wszystko tutaj”, której jedynym zadaniem jest wywołać wszystkie te mniejsze funkcje, takie jak:
void largeReportProcessingFunction()
{
processSection1HeaderData();
calculateSection1HeaderAverages();
formatSection1HeaderDisplay();
processSection1SummaryTableData();
calculateSection1SummaryTableTotalRow();
formatSection1SummaryTableDisplay();
processSection1FooterData();
getSection1FooterSummaryTotals();
formatSection1FooterDisplay();
processSection2HeaderData();
calculateSection1HeaderAverages();
formatSection1HeaderDisplay();
calculateSection1HeaderAverages();
...
}
Lub jeśli pójdziemy o krok dalej:
void largeReportProcessingFunction()
{
callAllSection1Functions();
callAllSection2Functions();
callAllSection3Functions();
...
}
Czy to naprawdę lepsze rozwiązanie? Z organizacyjnego punktu widzenia przypuszczam, że tak (tzn. Wszystko jest o wiele lepiej zorganizowane, niż mogłoby być inaczej), ale nie jestem pewien co do czytelności kodu (potencjalnie duże łańcuchy funkcji, które wywołują tylko inne funkcje).
Myśli?
źródło
Odpowiedzi:
Jest to powszechnie określane jako rozkład funkcjonalny i ogólnie jest dobrym rozwiązaniem, jeśli jest wykonane poprawnie.
Ponadto implementacja funkcji powinna mieścić się w obrębie jednego poziomu abstrakcji. Jeśli weźmiesz
largeReportProcessingFunction
, to jego rolą jest określenie, jakie różne kroki przetwarzania należy podjąć w jakiej kolejności. Implementacja każdego z tych kroków znajduje się na poniższej warstwie abstrakcji ilargeReportProcessingFunction
nie powinna zależeć od niego bezpośrednio.Pamiętaj jednak, że to zły wybór nazywania:
Widzisz,
callAllSection1Functions
to nazwa, która w rzeczywistości nie zapewnia abstrakcji, ponieważ tak naprawdę nie mówi, co robi, ale jak to robi.processSection1
Zamiast tego należy go wywołać lub cokolwiek innego, co ma znaczenie w kontekście.źródło
callAllSection1Functions
wycieki szczegóły dotyczące implementacji. Z zewnętrznego punktu widzenia nie ma znaczenia, czyprocessSection1
odracza swoją pracę do „wszystkich funkcji sekcji 1”, czy też robi to bezpośrednio, czy też używa pixie-pyłu do przywołania jednorożca, który wykona rzeczywistą pracę. Wszystko to naprawdę sprawa jest taka, że po wywołaniuprocessSection1
, sekcja1 można uznać przetwarzane . Zgadzam się, że przetwarzanie jest ogólną nazwą, ale jeśli masz wysoką spójność (do której zawsze powinieneś dążyć), twój kontekst jest zwykle wystarczająco wąski, aby go jednoznacznie określić.Powiedziałeś, że używasz C #, więc zastanawiam się, czy możesz zbudować strukturalną hierarchię klas, wykorzystując fakt, że używasz języka obiektowego? Na przykład, jeśli sensowne jest podzielenie raportu na sekcje, należy mieć
Section
klasę i rozszerzyć ją o specyficzne wymagania klienta.Może mieć sens myśleć o tym, jakbyś tworzył nieinteraktywny GUI. Pomyśl o obiektach, które możesz stworzyć tak, jakby były różnymi komponentami GUI. Zadaj sobie pytanie, jak wyglądałby podstawowy raport? A może skomplikowany? Gdzie powinny być punkty rozszerzenia?
źródło
To zależy. Jeśli wszystkie funkcje, które tworzysz, są naprawdę pojedynczą jednostką pracy, to dobrze, jeśli są to tylko wiersze 1-15, 16-30, 31-45 ich funkcji wywoływania, która jest zła. Długie funkcje nie są złe, jeśli robią jedną rzecz, jeśli masz 20 filtrów do raportu, a funkcja sprawdzania poprawności, która sprawdza, że wszystkie dwadzieścia będzie długa. W porządku, dzielenie go według filtrów lub grup filtrów to tylko dodatkowa złożoność bez rzeczywistych korzyści.
Posiadanie wielu prywatnych funkcji utrudnia również automatyczne testowanie jednostek, więc niektórzy z tego powodu będą starali się tego uniknąć, ale moim zdaniem jest to raczej słabe rozumowanie, ponieważ ma tendencję do tworzenia większego, bardziej złożonego projektu do dostosowania do testów.
źródło
Ponieważ używasz C #, spróbuj pomyśleć więcej w obiektach. Wygląda na to, że nadal kodujesz proceduralnie, mając zmienne klasy „globalnej”, od których zależą kolejne funkcje.
Przełamanie logiki w oddzielnych funkcjach jest zdecydowanie lepsze niż posiadanie całej logiki w jednej funkcji. Założę się, że możesz pójść dalej, oddzielając dane, na których działają funkcje, dzieląc je na kilka obiektów.
Zastanów się nad klasą ReportBuilder, w której możesz przekazać dane raportu. Jeśli możesz podzielić ReportBuilder na osobne klasy, zrób to również.
źródło
Tak.
Jeśli masz segmenty kodu, które muszą być używane w wielu miejscach, własna funkcja. W przeciwnym razie, jeśli musisz zmienić funkcjonalność, będziesz musiał dokonać zmiany w wielu miejscach. Zwiększa to nie tylko pracę, ale także zwiększa ryzyko pojawienia się błędów, gdy kod zmienia się w jednym miejscu, ale nie w innym, lub w miejscu, w którym wprowadza się znak „ale”, ale nie w drugim.
Pomaga również uczynić metodę bardziej czytelną, jeśli zostaną użyte opisowe nazwy metod.
źródło
Fakt, że używasz numeracji do rozróżniania funkcji, sugeruje, że istnieją podstawowe problemy z powielaniem lub zbyt dużą klasą. Podział dużej funkcji na wiele mniejszych funkcji może być przydatnym krokiem w refaktoryzacji duplikacji, ale nie powinien być ostatnim. Na przykład tworzysz dwie funkcje:
Czy w kodzie używanym do przetwarzania nagłówków sekcji nie ma żadnych podobieństw? Czy możesz wyodrębnić wspólną funkcję dla obu z nich poprzez sparametryzowanie różnic?
źródło
Podział funkcji na logiczne podfunkcje jest pomocny, nawet jeśli podfunkcje nie są ponownie używane - dzieli je na krótkie, łatwe do zrozumienia bloki. Ale musi to być zrobione przez jednostki logiczne, a nie tylko n # linii. To, jak powinieneś to rozbić, będzie zależeć od twojego kodu, wspólne tematy to pętle, warunki, operacje na tym samym obiekcie; w zasadzie wszystko, co można opisać jako zadanie dyskretne.
Tak, tak, to dobry styl - może to zabrzmieć dziwnie, ale biorąc pod uwagę twoje ograniczone wykorzystanie, polecam dokładnie przemyśleć PRÓBĘ ponownego użycia. Upewnij się, że użycie jest naprawdę identyczne, a nie tylko przypadkowo takie samo. Próba obsłużenia wszystkich przypadków w tym samym bloku jest często sposobem, w jaki kończysz się z dużą niezgrabną funkcją w pierwszym miejscu.
źródło
Myślę, że to głównie osobiste preferencje. Jeśli twoje funkcje miałyby być ponownie użyte (i czy jesteś pewien, że nie możesz uczynić ich bardziej ogólnymi i wielokrotnego użytku?) Zdecydowanie powiedziałbym, że je rozdzielisz. Słyszałem również, że dobrą zasadą jest, aby żadna funkcja ani metoda nie zawierała więcej niż 2-3 ekrany kodu. Jeśli więc twoja duża funkcja będzie się powtarzać, kod może być bardziej czytelny, aby ją podzielić.
Nie wspominasz, jakiej technologii używasz do tworzenia tych raportów. Wygląda na to, że używasz C #. Czy to jest poprawne? Jeśli tak, możesz również rozważyć dyrektywę #region jako alternatywę, jeśli nie chcesz dzielić funkcji na mniejsze.
źródło