Abstrakcja: Wojna między rozwiązaniem problemu a ogólnym rozwiązaniem [zamknięte]

33

Jako programista znalazłem się w dylemacie, w którym chcę, aby mój program był jak najbardziej abstrakcyjny i jak najbardziej ogólny.

Takie postępowanie zazwyczaj pozwala mi ponownie użyć mojego kodu i uzyskać bardziej ogólne rozwiązanie problemu, który może (lub nie musi) pojawić się ponownie.

Wtedy ten głos w mojej głowie mówi: po prostu rozwiąż problem, manekin, to takie proste! Po co spędzać więcej czasu niż trzeba?

Wszyscy rzeczywiście zmierzyliśmy się z tym pytaniem, gdzie Abstrakcja jest na twoim prawym ramieniu, a Rozwiąż to głupie po lewej.

Czego słuchać i jak często? Jaka jest twoja strategia? Czy wszystko powinieneś streścić?

Bryan Harrington
źródło
1
„Tak abstrakcyjne i ogólne” jest ogólnie znane jako pycha programisty :) Jednak z powodu prawa Murphy'ego jeden stopień adaptacji, którego będziesz potrzebować podczas pracy nad kolejnym zadaniem, będzie tym, którego nie podałeś.
Matthieu M.
1
Jedno z najlepszych pytań w historii!
odkrywca
1
Zawsze zgadujesz źle, więc zrób to prosto. Po rozwinięciu prostych przypadków „wystarczająco dużo razy” zaczniesz widzieć wzór. Do tego czasu nie rób.

Odpowiedzi:

27

Czego słuchać i jak często?

Nigdy nie abstrakcyjne, dopóki nie musisz.

Na przykład w Javie musisz używać interfejsów. Są abstrakcją.

W Pythonie nie masz interfejsów, masz Duck Typing i nie potrzebujesz wysokich poziomów abstrakcji. Więc po prostu nie.

Jaka jest twoja strategia?

Nie streszczaj, dopóki nie napiszesz tego trzy razy.

Raz jest - cóż - raz. Wystarczy go rozwiązać i przejść dalej.

Dwukrotnie oznacza, że ​​może istnieć tutaj wzór. Lub może nie. To może być po prostu zbieg okoliczności.

Trzykrotnie jest początkiem wzoru. Teraz wykracza poza przypadek. Możesz teraz pomyślnie streścić.

Czy wszystko powinieneś streścić?

Nie.

Rzeczywiście, nigdy nie powinieneś niczego abstrakować, dopóki nie masz absolutnego dowodu, że robisz właściwy rodzaj abstrakcji. Bez „Reguły trzech powtórzeń” napiszesz rzeczy bezużytecznie abstrakcyjne w sposób, który nie jest pomocny.

Takie postępowanie zazwyczaj pozwala mi na ponowne użycie mojego kodu

Jest to założenie, które często jest fałszywe. Abstrakcja może wcale nie pomóc. Można to zrobić źle. Dlatego nie rób tego, dopóki nie musisz.

S.Lott
źródło
1
„Nigdy nie abstrakcyjne, dopóki nie musisz”. - Rozumiem, że unikasz używania klas, metod, pętli lub instrukcji? Te wszystkie rzeczy są abstrakcjami. Jeśli się nad tym zastanowić, kod napisany w językach wysokiego poziomu jest bardzo abstrakcyjny, czy mu się to podoba, czy nie. Pytanie nie brzmi, czy abstrahować. To do jakich abstrakcji użyć.
Jason Baker
9
@Jason Baker: „Rozumiem, że unikasz używania klas, metod, pętli lub instrukcji if”. Wydaje się, że nie o to chodziło w tym pytaniu. Po co absurdalnie twierdzić, że całe programowanie jest niemożliwe, jeśli unikamy nadmiernej abstrakcji naszych projektów?
S.Lott,
1
Przypomina mi się stary żart, w którym mężczyzna pyta kobietę, czy będzie spała z nim za milion dolarów. Kiedy mówi „tak”, pyta, czy będzie spała z nim za pięć dolarów. Kiedy mówi: „Za jaką kobietę mnie zabierasz?” odpowiedź brzmi: „Cóż, już ustaliliśmy, że będziesz spać z kimś dla seksu. Teraz tylko targujemy się o cenę”. Chodzi mi o to, że nigdy nie chodzi o decyzję o abstrakcji czy nie. Chodzi o to, ile abstrakcji i jakie abstrakcje wybrać. Nie możesz powstrzymać się od abstrakcji, chyba że piszesz czysty zbiór.
Jason Baker
9
@Jason Baker: „Nie możesz powstrzymać się od abstrakcji, dopóki nie piszesz czystego asemblera” To jest trywialnie nieprawdziwe. Asemblacja jest abstrakcją nad językiem maszynowym. Co jest abstrakcją w stosunku do obwodów sprzętowych. Proszę, przestańcie czytać zbyt wiele w odpowiedzi, która w ogóle nie była kwestionowana. Pytanie nie dotyczyło „abstrakcji” jako koncepcji czy narzędzia intelektualnego. Chodziło o abstrakcję jako technikę projektowania OO - niewłaściwie stosowaną - zbyt często. Moja odpowiedź nie była taka, że ​​abstrakcja jest niemożliwa. Ale ta „nadmierna abstrakcja” jest zła.
S.Lott
1
@JasonBaker to nie jest niespotykany powód do spania z kimś. Może myślałeś o „pieniądzach”?
19

Ach, YAGNI. Najbardziej nadużywana koncepcja programowania.

Istnieje różnica między dostosowaniem kodu a wykonaniem dodatkowej pracy. Czy powinieneś poświęcić dodatkowy czas na luźne powiązanie kodu i łatwą adaptację do innych celów? Absolutnie. Czy powinieneś spędzać czas na wdrażaniu niepotrzebnych funkcji? Nie. Czy powinieneś poświęcić czas na dostosowanie kodu do innego kodu, który nie został jeszcze zaimplementowany? Nie.

Jak mówi przysłowie: „Zrób najprostszą rzecz, która może działać”. Chodzi o to, że ludzie zawsze mylą proste z łatwym . Prostota wymaga pracy. Ale warto spróbować.

Czy możesz wyjść za burtę? Oczywiście. Ale z mojego doświadczenia wynika, że ​​niewiele firm potrzebuje bardziej podejścia „zrób to teraz” niż ma to już miejsce. Większość firm potrzebuje więcej osób, które przemyślą wszystko z wyprzedzeniem. W przeciwnym razie zawsze kończą się samowystarczalnym problemem, w którym nikt nigdy nie ma czasu na nic, wszyscy są w pośpiechu i nikt nic nie robi.

Pomyśl o tym w ten sposób: są duże szanse, że Twój kod zostanie jakoś ponownie wykorzystany. Ale nie zamierzasz poprawnie przewidzieć w ten sposób. Dlatego twój kod powinien być czysty, abstrakcyjny i luźno powiązany. Ale nie staraj się, aby Twój kod działał z konkretnymi funkcjami, które jeszcze nie istnieją. Nawet jeśli wiesz, że będzie istniał w przyszłości.

Jason Baker
źródło
Niezłe rozróżnienie między simplei easy!
Matthieu M.
„Ale z mojego doświadczenia wynika, że ​​niewiele firm” - co ciekawe, odwrotność może być prawdziwa w środowisku akademickim.
Steve Bennett
12

Syllogizm:

  1. Generalność jest droga.

  2. Wydajesz pieniądze innych ludzi.

  3. Dlatego koszt ogólności musi być uzasadniony dla zainteresowanych stron.

Zadaj sobie pytanie, czy naprawdę rozwiązujesz bardziej ogólny problem, aby później zaoszczędzić pieniądze interesariuszy, czy może po prostu uważasz, że podjęcie niepotrzebnie ogólnego rozwiązania stanowi wyzwanie intelektualne.

Jeśli ogólność zostanie uznana za pożądaną , należy ją zaprojektować i przetestować jak każdą inną cechę . Jeśli nie możesz napisać testów, które pokazują, w jaki sposób ogólność, którą zaimplementowałeś, rozwiązuje problem wymagany przez specyfikację, nie zawracaj sobie tym głowy! Funkcja, która nie spełnia żadnych kryteriów projektowych i nie może być przetestowana, jest funkcją, na której nikt nie może polegać.

I na koniec, nie ma czegoś takiego jak „tak ogólne, jak to możliwe”. Załóżmy, że piszesz oprogramowanie w języku C #, tylko ze względu na argument. Czy chcesz, aby każda klasa implementowała interfejs? Każda klasa abstrakcyjna klasa bazowa z każdą metodą abstrakcyjną metodą? To dość ogólne, ale nie jest to wcale „tak ogólne, jak to możliwe”. To pozwala ludziom zmieniać implementację dowolnej metody poprzez podklasowanie. Co jeśli chcą zmienić implementację metody bez podklasy? Możesz uczynić każdą metodę faktycznie właściwością typu delegowanego, z ustawiaczem, aby ludzie mogli zmienić każdą metodę na coś innego. Ale co, jeśli ktoś chce dodać więcej metod? Teraz każdy obiekt musi być rozszerzalny.

W tym momencie powinieneś porzucić C # i włączyć JavaScript. Ale wciąż nie jesteś wystarczająco ogólny; co jeśli ktoś chce zmienić sposób, w jaki działa wyszukiwanie członków dla tego konkretnego obiektu? Może zamiast tego powinieneś napisać wszystko w Pythonie.

Rosnąca ogólność często oznacza porzucenie przewidywalności i masywne zwiększenie kosztów testowania, aby zapewnić, że zaimplementowana ogólność rzeczywiście spełni rzeczywiste potrzeby użytkowników . Czy koszty te są uzasadnione korzyściami dla interesariuszy, którzy za nie płacą? Być może są; to do ciebie należy rozmowa z interesariuszami. Moi interesariusze wcale nie są skłonni do porzucenia pisania statycznego w celu uzyskania całkowicie niepotrzebnego i niezwykle kosztownego poziomu ogólności, ale być może są twoje.

Eric Lippert
źródło
2
+1 za przydatne zasady, -1 za zarabianie pieniędzy.
Mason Wheeler,
4
@Mason: Tu nie chodzi o pieniądze , chodzi o wysiłek . Pieniądze są praktyczną miarą wysiłku . Efektywność to korzyści naliczane na wyprodukowany wysiłek; znowu zysk pieniężny jest praktyczną miarą naliczonych świadczeń . Po prostu łatwiej jest omówić abstrakcję pieniędzy niż wymyślić jakiś sposób pomiaru wysiłku, kosztów i korzyści. Czy wolisz mierzyć wysiłek, koszty i korzyści w inny sposób? Zapraszam do przedstawienia propozycji lepszego sposobu.
Eric Lippert,
2
Zgadzam się z punktem @Mason Wheeler. Uważam, że rozwiązaniem jest nie angażowanie interesariuszy na tym poziomie projektu. Oczywiście zależy to w dużej mierze od branży, ale klienci zwykle nie widzą kodu. Ponadto wszystkie te odpowiedzi wydają się być anty-wysiłkowe, wydają się leniwe.
Orbling
2
@Orbling: Jestem zdecydowanie przeciwny niepotrzebnemu, drogiemu i marnotrawstwu, który odbiera zasoby godnym i ważnym projektom. Jeśli sugerujesz, że to czyni mnie „leniwym”, to twierdzę, że używasz słowa „leniwy” w niecodzienny sposób.
Eric Lippert,
1
Z mojego doświadczenia wynika, że ​​inne projekty zwykle nie odchodzą, więc zwykle warto zainwestować tyle czasu, ile potrzeba w bieżący projekt przed przejściem dalej.
Orbling
5

Żeby było jasne, zrobienie czegoś uogólnionego i wdrożenie abstrakcji to dwie zupełnie różne rzeczy.

Rozważmy na przykład funkcję kopiującą pamięć.

Ta funkcja jest abstrakcją, która ukrywa sposób kopiowania 4 bajtów.

int copy4Bytes (char * pSrc, char * pDest)

Uogólnienie polegałoby na skopiowaniu przez tę funkcję dowolnej liczby bajtów.

int copyBytes (char * pSrc, char * pDest, int numBytesToCopy)

Abstrakcja nadaje się do ponownego użycia, podczas gdy uogólnienie sprawia, że ​​abstrakcja jest przydatna w wielu przypadkach.

Bardziej konkretnie związane z twoim pytaniem, Abstrakcja jest przydatna nie tylko z perspektywy ponownego użycia kodu. Często poprawne wykonanie kodu sprawi, że Twój kod będzie bardziej czytelny i łatwy w utrzymaniu. Korzystając z powyższego przykładu, co jest łatwiejsze do odczytania i zrozumienia, jeśli przeglądasz kod copyBytes () lub pętlę for iterującą po tablicy przenoszącej dane jeden indeks na raz? Abstrakcja może zapewnić rodzaj własnej dokumentacji, która moim zdaniem ułatwia pracę z kodem.

Zgodnie z moją podstawową zasadą, jeśli mogę wymyślić dobrą nazwę funkcji, która dokładnie opisuje to, co zamierzam zrobić, to piszę dla niej funkcję, niezależnie od tego, czy myślę, że użyję jej ponownie.

Pemda
źródło
+1 za różnicę między abstrakcją a uogólnieniem.
Joris Meys
4

Dobrą ogólną zasadą tego rodzaju rzeczy jest zero, jeden, nieskończoność. Oznacza to, że jeśli potrzebujesz tego, co piszesz więcej niż jeden raz, możesz założyć, że będziesz potrzebować tego jeszcze więcej razy i uogólnij. Ta zasada oznacza, że ​​nie zawracasz sobie głowy abstrakcją przy pierwszym napisaniu czegoś.

Innym dobrym powodem tej reguły jest to, że przy pierwszym pisaniu kodu niekoniecznie będziesz wiedział, co abstrahować, ponieważ masz tylko jeden przykład. Prawo Murphy'ego oznacza, że ​​jeśli po raz pierwszy napiszesz kod abstrakcyjny, drugi przykład będzie miał różnice, których się nie spodziewałeś.

Larry Coleman
źródło
1
To brzmi bardzo podobnie do mojej wersji Reguły Refaktoryzacji: uderz dwa! refaktor!
Frank Shearar,
@Frank: Prawdopodobnie jest to twoja Reguła Refaktoryzacji, ale z drugim akapitem dodanym z własnego doświadczenia.
Larry Coleman
+1: Wierzę, że zauważono to również dość wcześnie w The Pragmatic Programmer prawie dosłownie IIRC.
Steven Evers
o zdecydowanie. Tylko jeden przykład nie ma nic do abstrakcji: jak tylko masz drugi przykład, możesz zobaczyć, co jest wspólne (wspólne), a co nie, i możesz abstrahować!
Frank Shearar,
Próbka dwóch jest moim zdaniem zbyt mała, aby uogólnić na nieskończoność. Innymi słowy, o wiele za wcześnie.
2

Eric Lippert zwraca uwagę na trzy rzeczy, które moim zdaniem mają zastosowanie w jego artykule dotyczącym przyszłego projektu . Myślę, że jeśli będziesz go przestrzegać, będziesz w dobrej formie.

Po pierwsze: przedwczesna ogólność jest droga.

Po drugie: reprezentuj w swoim modelu tylko te rzeczy, które są zawsze w domenie problemowej i których relacje klasowe są niezmienne.

Po trzecie: trzymaj swoje zasady z dala od mechanizmów.

Conrad Frix
źródło
1

To zależy od tego, dlaczego programujesz, od celu twojego projektu. Jeśli wartość twojego kodu rozwiązuje konkretny problem, to chcesz to zrobić i przejść do następnego problemu. Jeśli istnieją szybkie i łatwe rzeczy, które możesz zrobić, aby ułatwić przyszłym użytkownikom kodu (w tym tobie), to zrób wszystko, co należy, aby zapewnić odpowiednie zakwaterowanie.

Z drugiej strony zdarzają się przypadki, gdy kod, który piszesz, ma bardziej ogólny cel. Na przykład, gdy piszesz bibliotekę dla innych programistów, którzy będą jej używać w wielu różnych aplikacjach. Potencjalni użytkownicy są nieznani i nie możesz zapytać ich dokładnie, czego chcą. Ale chcesz, aby Twoja biblioteka była ogólnie użyteczna. W takich przypadkach spędzam więcej czasu próbując wesprzeć ogólne rozwiązanie.

Rob Weir
źródło
0

Jestem wielkim fanem zasady KISS.

Skupiam się na zapewnieniu tego, o co mnie proszono, a nie na najlepszym rozwiązaniu. Musiałem porzucić perfekcjonizm (i OCD), ponieważ sprawiało to, że byłem nieszczęśliwy.

Pablo
źródło
Dlaczego niechęć do perfekcjonizmu? (
Nawiasem mówiąc,
Nie dążenie do perfekcji działa dla mnie. Czemu? Ponieważ wiem, że moje programy nigdy nie będą idealne. Nie ma standardowej definicji doskonałości, więc po prostu wybrałem coś, co działa dobrze.
Pablo
-1

gdzie Abstrakcja jest na twoim prawym ramieniu, a Rozwiąż to głupie po lewej.

Nie zgadzam się z „rozwiązać to głupio” , myślę, że może być bardziej „rozwiązać to mądrze” .

Co jest mądrzejsze:

  • Pisanie złożonego uogólnionego rozwiązania, które może obsługiwać wiele przypadków
  • Pisanie krótkich i skuteczny kod, który rozwiązuje problem pod ręką i jest łatwy w utrzymaniu, a które mogą być rozszerzone w przyszłości IF jest to konieczne.

Domyślnym wyborem powinien być drugi. Chyba że potrafisz wykazać potrzebę pokoleń.

Uogólnione rozwiązanie powinno być dostępne tylko wtedy, gdy wiesz, że jest to coś, co zostanie wykorzystane w wielu różnych projektach / przypadkach.

Zazwyczaj uważam, że uogólnione rozwiązania najlepiej nadają się jako „kod biblioteki podstawowej”

Ciemna noc
źródło
Gdybyś mógł tutaj przyjąć wszystkie założenia, nie miałbyś problemu. Krótkie i łatwe w utrzymaniu wykluczają się wzajemnie. Jeśli wiesz, że coś zostanie ponownie wykorzystane, ogólne rozwiązanie to rozwiązałoby.
JeffO
@Jeff Nie jestem pewien, czy rozumiem twój komentarz.
Darknight,
Wyjąłeś „Solve it Stupid” z kontekstu.
Bryan Harrington