Jak określić poziomy abstrakcji


35

Czytałem dziś książkę zatytułowaną „Czysty kod” i natknąłem się na akapit, w którym autor mówił o poziomach abstrakcji dla funkcji, sklasyfikował jakiś kod jako niski / średni / wysoki poziom abstrakcji.

Moje pytanie brzmi: jakie są kryteria określania poziomu abstrakcji?

Cytuję akapit z książki:

Aby upewnić się, że nasze funkcje wykonują „jedną rzecz”, musimy upewnić się, że wszystkie instrukcje w ramach naszej funkcji są na tym samym poziomie abstrakcji. Łatwo jest zobaczyć, jak Listing 3-1 narusza tę zasadę. Istnieją tam koncepcje o bardzo wysokim poziomie abstrakcji, takie jak getHtml (); inne, które są na pośrednim poziomie abstrakcji, takie jak: String pagePathName = PathParser.render (pagePath); i jeszcze inne, które są wyjątkowo niskie, takie jak: .append ("\ n").


Odpowiedzi:


27

Autor wyjaśnia, że ​​w podsekcji „Czytanie kodu od góry do dołu” części, która mówi o abstrakcjach (moje wcięcie hierarchiczne):

[...] chcemy móc czytać program tak, jakby to był zestaw akapitów TO , z których każdy opisuje aktualny poziom abstrakcji i odwołuje się do kolejnych akapitów TO na następnym poziomie w dół.

  • Aby uwzględnić konfiguracje i rozłączenia, uwzględniamy konfiguracje, następnie uwzględniamy zawartość strony testowej, a następnie uwzględniamy rozbiegi.
    • Aby uwzględnić konfiguracje, dołączamy konfigurację pakietu, jeśli jest to pakiet, a następnie włączamy standardową konfigurację.
      • Aby uwzględnić konfigurację pakietu, przeszukujemy hierarchię nadrzędną strony „SuiteSetUp” i dodajemy instrukcję include ze ścieżką tej strony.
        • Aby wyszukać rodzica ...

Kod, który byłby z tym zgodny, wyglądałby mniej więcej tak:

public void CreateTestPage()
{
    IncludeSetups();
    IncludeTestPageContent();
    IncludeTeardowns();
}

public void IncludeSetups()
{
    if(this.IsSuite())
    {
        IncludeSuiteSetup();
    }

    IncludeRegularSetup();
}

public void IncludeSuiteSetup()
{
    var parentPage = FindParentSuitePage();

    // add include statement with the path of the parentPage
}

I tak dalej. Za każdym razem, gdy wchodzisz głębiej w hierarchię funkcji, powinieneś zmieniać poziomy abstrakcji. W przykładzie powyżej IncludeSetups, IncludeTestPageContenti IncludeTeardownssą na tym samym poziomie abstrakcji.

W przykładzie podanym w książce autor sugeruje, że dużą funkcję należy podzielić na mniejsze, które są bardzo specyficzne i robią tylko jedną rzecz. Jeśli zrobione poprawnie, funkcja refaktoryzowana wyglądałaby podobnie do przykładów tutaj. (Refaktoryzowana wersja znajduje się na listingu 3-7 w książce.)


10

Myślę, że aby zrozumieć to pytanie, musisz zrozumieć, czym jest abstrakcja. (Jestem zbyt leniwy, by znaleźć formalną definicję, więc jestem pewien, że zaraz się oszukam, ale oto idzie ...) Abstrakcja dotyczy sytuacji, w których bierzesz skomplikowany temat lub byt i ukrywasz większość jego szczegółów ujawniając funkcjonalność, która wciąż definiuje istotę tego obiektu.

Wierzę, że przykładem, który dała ci książka, był dom. Jeśli przyjrzysz się bardzo szczegółowo domowi, zobaczysz, że jest zrobiony z desek, gwoździ, okien, drzwi ... Ale rysunek domu obok zdjęcia jest nadal domem, mimo że go brakuje wiele z tych szczegółów.

To samo dotyczy oprogramowania. Ilekroć programujesz, tak jak radzi książka, musisz myśleć o swoim oprogramowaniu jako o warstwach. Dany program może mieć z łatwością ponad sto warstw. Na dole mogą znajdować się instrukcje asemblera działające na procesorze, na wyższym poziomie instrukcje te można łączyć w celu utworzenia procedur we / wy dysku, na jeszcze wyższym poziomie nie trzeba pracować z dyskowym we / wy O bezpośrednio, ponieważ możesz użyć funkcji systemu Windows, aby po prostu otworzyć / odczytać / zapisać / wyszukać / zamknąć plik. Są to wszystkie abstrakty, nawet zanim przejdziesz do własnego kodu aplikacji.

W twoim kodzie warstwy abstrakcji są kontynuowane. Mogą istnieć procedury manipulacji ciągami / siecią / danymi niższego poziomu. Na wyższym poziomie można łączyć te procedury w podsystemy, które definiują zarządzanie użytkownikami, warstwę interfejsu użytkownika, dostęp do bazy danych. Jeszcze inna warstwa te podsystemy mogą być łączone w komponenty serwera, które łączą się, aby stać się częścią większego systemu korporacyjnego.

Kluczem do każdej z tych warstw abstrakcji jest to, że każda z nich ukrywa szczegóły odsłonięte przez poprzednie warstwy (warstwy) i zapewnia bardzo czysty interfejs do wykorzystania przez kolejne warstwy. Aby otworzyć plik, nie powinieneś wiedzieć, jak pisać poszczególne sektory ani jakiego sprzętu przerwać. Ale jeśli zaczniesz podróżować w dół łańcucha warstwy abstrakcji, na pewno będziesz w stanie prześledzić wywołanie funkcji Write () aż do dokładnej instrukcji wysyłanej do kontrolera dysku twardego.

Autor mówi ci, abyś zdefiniował klasę lub funkcję, zastanów się, na jakiej warstwie jesteś. Jeśli masz klasę zarządzającą podsystemami i obiektami użytkownika, ta sama klasa nie powinna wykonywać niskopoziomowych operacji na łańcuchach ani zawierać całej gamy zmiennych tylko do wywoływania gniazd. Byłoby to naruszeniem przekraczania warstw abstrakcji, a także posiadania jednej klasy / funkcji, by wykonywać tylko jedną rzecz (SRP - zasada pojedynczej odpowiedzialności).


2

Moje pytanie brzmi: jakie są kryteria określania poziomu abstrakcji?

Poziom abstrakcji powinien być oczywisty. Jest abstrakcyjny, jeśli jest częścią domeny problemowej, a nie częścią języka programowania. Trudno jest być bardziej zrozumiałym niż „wysoce abstrakcyjny” == „nierealny” == „problematyczna domena”. I „nie abstrakcyjny == konkretny == część języka”. Wybór poziomu abstrakcji powinien być trywialny. Nie powinno być żadnej subtelności.

.append("\n")nie jest abstrakcyjne. Po prostu umieszcza znak na sznurku. To byłoby konkretne. Nie abstrakcyjne.

String pagePathName = PathParser.render(pagePath);zajmuje się Strings. Konkretne rzeczy. Częściowo na konkretnych funkcjach języka programowania. Częściowa praca z abstrakcyjnymi koncepcjami „ścieżki” i „parsera”.

getHtml(); Abstrakcyjny. Zajmuje się „znacznikami” i rzeczami, które nie są trywialnymi, konkretnymi funkcjami językowymi.

Streszczenie == nie jest funkcją językową.

Beton == funkcja języka.


1
Jeśli zdefiniujesz abstrakcję jako jakość opisującą rzeczy tworzone przez programistę, tj. Abstrakcje generowane przez programistę, to jestem skłonny zgodzić się z twoimi definicjami słów. Ale wszystkie języki programowania są abstrakcjami nad czymś bardziej konkretnym. Wybór języka programowania w dużej mierze determinuje poziom abstrakcji, od którego zaczynasz.
Robert Harvey

1

Myślę, że poziom abstrakcji jest prosty ... jeśli linia kodu nie realizuje bezpośrednio pojedynczej odpowiedzialności za metodę, jest to kolejny poziom abstrakcji. Na przykład, jeśli nazwa mojej metody to SaveChangedCustomers () i jako parametr przyjmuje listę WSZYSTKICH klientów, jedynym obowiązkiem jest zapisanie wszystkich klientów na liście, które zostały zmienione:

foreach(var customer in allCustomers)
{
    if (CustomerIsChanged(customer)
        customer.Save();
}

Często zamiast wywoływać metodę CustomerIsChanged (), znajdziesz logikę określającą, czy klient zmienił się w pętli foreach. Ustalenie, czy rekord klienta się zmienił NIE jest obowiązkiem tej metody! To inny poziom abstrakcji. Ale co, jeśli logika, aby dokonać tego ustalenia, jest tylko jednym wierszem kodu? To nie ma znaczenia !!! Jest to inny poziom abstrakcji i musi być poza tą metodą.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.