Jak zmniejszyć wysiłek związany z ręcznym pakowaniem bibliotek stron trzecich w większy model obiektowy?

16

Podobnie jak autor tego pytania z 2012 roku i tego z 2013 roku , mam bibliotekę strony trzeciej, którą muszę spakować, aby poprawnie przetestować swoją aplikację. Najlepsza odpowiedź brzmi:

Zawsze chcesz zawijać typy i metody stron trzecich za interfejsem. Może to być nudne i bolesne. Czasami możesz napisać generator kodu lub użyć do tego narzędzia.

W moim przypadku biblioteka jest przeznaczona dla modelu obiektowego, w związku z czym ma większą liczbę klas i metod, które należałoby zapakować, aby strategia była skuteczna. Oprócz zwykłego „nudnego i bolesnego” staje się to trudną barierą dla testów.

W ciągu 4 lat od tego pytania zdaję sobie sprawę, że ramy izolacyjne przeszły długą drogę. Moje pytanie brzmi: czy istnieje teraz prostszy sposób na osiągnięcie efektu pełnego pakowania bibliotek stron trzecich? Jak mogę usunąć ból z tego procesu i zmniejszyć wysiłek manualny?


Moje pytanie nie jest duplikatem pytań, z którymi początkowo się połączyłem, ponieważ moje dotyczy zmniejszenia ręcznego nakładania opakowania. Te pozostałe pytania dotyczą tylko tego, czy owijanie ma sens, a nie tego, w jaki sposób wysiłek może być niewielki.

Tom Wright
źródło
W jakim języku programowania i o jakiej bibliotece mówisz?
Dok. Brown
@DocBrown C # i biblioteka manipulacji PDF.
Tom Wright,
2
Zacząłem pisać na stronie meta, aby znaleźć wsparcie dla ponownego otwarcia pytania.
Dok. Brown
Dzięki @DocBrown - Mam nadzieję, że będzie tam kilka interesujących perspektyw.
Tom Wright,
1
Kiedy nie otrzymamy lepszych odpowiedzi w ciągu następnych 48 godzin, naliczę za to nagrodę.
Doc Brown

Odpowiedzi:

4

Zakładając, że nie szukasz kpiącego frameworka, ponieważ są one wszechobecne i łatwe do znalezienia , jest kilka rzeczy, na które warto zwrócić uwagę:

  1. „Nigdy” nic nie powinieneś „zawsze” robić.
    Nie zawsze najlepiej jest zawinąć bibliotekę strony trzeciej. Jeśli twoja aplikacja jest wewnętrznie zależna od biblioteki lub jeśli jest dosłownie zbudowana wokół jednej lub dwóch podstawowych bibliotek, nie marnuj czasu na jej zamykanie. Jeśli biblioteki się zmienią, aplikacja i tak będzie musiała się zmienić .
  2. Można używać testów integracyjnych.
    Jest to szczególnie prawdziwe w przypadku granic, które są stabilne, nieodłączne dla aplikacji lub których nie można łatwo wyśmiewać. Jeśli te warunki zostaną spełnione, pakowanie i kpiny będą skomplikowane i nużące. W takim przypadku unikałbym obu: nie zawijaj i nie kpij; po prostu napisz testy integracyjne. (Jeśli celem jest automatyczne testowanie).
  3. Narzędzia i środowisko nie mogą wyeliminować logicznej złożoności.
    Zasadniczo narzędzie może ciąć tylko na płycie kotłowej. Ale nie ma algorytmu automatyzującego przyjmowanie złożonego interfejsu i upraszczanie go - nie mówiąc już o przyjmowaniu interfejsu X i dostosowywaniu go do własnych potrzeb. (Tylko ty znasz ten algorytm!) Tak więc, chociaż istnieją niewątpliwie narzędzia, które mogą generować cienkie opakowania, sugerowałbym, że nie są one już wszechobecne, ponieważ w końcu nadal musisz po prostu inteligentnie, a zatem ręcznie, kodować, przeciwko interfejsowi, nawet jeśli jest ukryty za opakowaniem.

To powiedziawszy, istnieją taktyki, których można użyć w wielu językach, aby uniknąć odwoływania się bezpośrednio do klasy. W niektórych przypadkach możesz „udawać” interfejs lub cienkie opakowanie, które tak naprawdę nie istnieje. Na przykład w języku C # wybrałbym jedną z dwóch tras:

  1. Użyj fabrycznego i niejawnego pisania .

Dzięki temu niewielkiemu zestawowi możesz uniknąć wysiłku pełnego pakowania złożonej klasy:

// "factory"
class PdfDocumentFactory {
  public static ExternalPDFLibraryDocument Build() {
    return new ExternalPDFLibraryDocument();
  }
}

// code that uses the factory.
class CoreBusinessEntity {
  public void DoImportantThings() {
    var doc = PdfDocumentFactory.Build();

    // ... i have no idea what your lib does, so, I'm making stuff but.
    // but, you can do whatever you want here without explicitly
    // referring to the library's actual types.
    doc.addHeader("Wee");
    doc.getAllText().makeBiggerBy(4).makeBold().makeItalic();
    return doc.exportBinaryStreamOrSomething();
  }
}

Jeśli można uniknąć zapisywania tych obiektów jako członków, poprzez bardziej „funkcjonalny” podejścia lub przechowując je w słowniku (lub cokolwiek ), takie podejście ma tę zaletę sprawdzenia typu kompilacji bez swoich kluczowych podmiotów gospodarczych, którzy muszą wiedzieć dokładnie z jaką klasą pracują.

Wszystko, co jest wymagane, to aby w czasie kompilacji klasa zwrócona przez twoją fabrykę faktycznie posiadała metody używane przez obiekt biznesowy.

  1. Użyj dynamicznego pisania .

Podobnie jest w przypadku pisania niejawnego , ale wiąże się to z innym kompromisem: tracisz kontrole typu kompilacyjnego i zyskujesz możliwość anonimowego dodawania zależności zewnętrznych jako członków klasy i wstrzykiwania zależności.

class CoreBusinessEntity {
  dynamic Doc;

  public void InjectDoc(dynamic Doc) {
    Doc = doc;
  }

  public void DoImortantThings() {
    Doc.addHeader("Wee");
    Doc.getAllText().makeBiggerBy(4).makeBold().makeItalic();
    return Doc.exportBinaryStreamOrSomething();
  }
}

Z obu tych taktyk, gdy przychodzi czas na Mock ExternalPDFLibraryDocument, jak wspomniano wcześniej, masz coś do zrobienia - ale jest to praca, którą trzeba zrobić, w każdym razie . Dzięki tej konstrukcji unikniesz żmudnego definiowania setek cienkich klas opakowań. Po prostu korzystałeś z biblioteki, nie patrząc bezpośrednio na nią - w większości.

Biorąc to wszystko pod uwagę, istnieją trzy ważne powody, dla których nadal rozważałbym jawne zamknięcie biblioteki strony trzeciej - żadna z nich nie sugerowałaby użycia narzędzia lub frameworka:

  1. Określona biblioteka nie jest nieodłączna dla aplikacji.
  2. Zamiana bez zawijania byłaby bardzo droga.
  3. Nie podoba mi się sam interfejs API.

Jeśli nie mam obaw o poziom we wszystkich tych trzech obszarach, nie podejmujesz żadnego znaczącego wysiłku, aby to podsumować. A jeśli masz jakieś obawy we wszystkich trzech obszarach, automatycznie wygenerowane cienkie opakowanie naprawdę nie pomoże.

Jeśli zdecydowałeś się owinąć biblioteka w górę, najbardziej skuteczne i efektywne wykorzystanie czasu jest, aby budować swoją aplikację przed interfejsu ty chcesz ; nie przeciwko istniejącemu API.

Innymi słowy, posłuchaj klasycznej rady: odłóż każdą decyzję, jaką możesz. Najpierw zbuduj „rdzeń” aplikacji. Kod przeciwko interfejsom, które w końcu zrobią to, co chcesz, co w końcu zostanie spełnione przez „peryferia”, które jeszcze nie istnieją. Uzupełnij luki według potrzeb.

Ten wysiłek może nie wydawać się oszczędnością czasu; ale jeśli uważasz, że potrzebujesz opakowania, jest to najbardziej skuteczny sposób, aby zrobić to bezpiecznie.

Pomyśl o tym w ten sposób.

Musisz zakodować tę bibliotekę w jakimś ciemnym rogu kodu - nawet jeśli jest on opakowany. Jeśli wyśmiejesz bibliotekę podczas testowania, nie da się uniknąć ręcznego wysiłku - nawet jeśli jest ona zapakowana. Nie oznacza to jednak, że musisz bezpośrednio potwierdzać tę bibliotekę według nazwy w większości aplikacji.

TLDR

Jeśli biblioteka jest warta pakowania, użyj taktyki, aby uniknąć powszechnych, bezpośrednich odniesień do biblioteki innej firmy, ale nie używaj skrótów do generowania cienkich opakowań. Najpierw zbuduj logikę biznesową, rozważnie swoje interfejsy i w razie potrzeby wyodrębnij adaptery w sposób organiczny .

A jeśli chodzi o to, nie bój się testów integracyjnych. Są trochę bardziej niepewne, ale wciąż oferują dowód działania kodu i nadal można je łatwo zrobić, aby powstrzymać regresję.

svidgen
źródło
2
To kolejny post nie odpowiadający na pytanie - które wyraźnie nie brzmiało „kiedy zawijać”, ale „jak zmniejszyć wysiłek manualny”.
Doc Brown
1
Przepraszam, ale myślę, że nie trafiłeś w sedno pytania. Nie rozumiem, w jaki sposób twoje sugestie mogłyby zmniejszyć wysiłek związany z ręcznym pakowaniem. Załóżmy, że przedmiotowa lib ma złożony model obiektowy jako API, z kilkoma klasami i setkami metod. Jego interfejs API jest w porządku, ponieważ jest przeznaczony do standardowego użytku, ale jak owinąć go do testów jednostkowych przy mniejszym bólu / wysiłku?
Doc Brown
1
TLDR; jeśli chcesz zdobyć punkty bonusowe, powiedz nam coś, czego jeszcze nie wiemy ;-)
Doc Brown
1
@DocBrown Nie przegapiłem sensu. Ale myślę , że nie trafiłeś w sedno mojej odpowiedzi. Zainwestowanie odpowiedniego wysiłku oszczędza dużo pracy. Istnieją implikacje związane z testowaniem - ale to tylko efekt uboczny. Korzystanie z narzędzia do automatycznego generowania cienkiego opakowania wokół biblioteki nadal pozostawia budowanie biblioteki podstawowej wokół interfejsu API innej osoby i tworzenie makiet - co jest dużym wysiłkiem, którego nie można uniknąć, jeśli nalegasz na pominięcie biblioteki twoich testów.
svidgen,
1
@DocBrown To absurdalne. „Czy ta klasa problemów ma klasę narzędzi lub podejść?” Tak. Oczywiście, że tak. Defensywne kodowanie i niepoprawianie dogmatów o testach jednostkowych ... jak mówi moja odpowiedź. Jeśli nie masz automagicznie wygenerowany cienką otoki, jaką wartość będzie dostarczać !? ... Nie pozwoli ci to na wstrzykiwanie zależności do testowania, wciąż musisz to zrobić ręcznie. I nie pozwoliłoby ci to zamienić biblioteki, ponieważ nadal kodujesz przeciwko API biblioteki ... to jest teraz tylko pośrednie.
svidgen,
9

Nie testuj tego kodu jednostkowo. Zamiast tego napisz testy integracyjne. W niektórych przypadkach testy jednostkowe, kpiny uciążliwe i bolesne. Porzuć testy jednostkowe i napisz testy integracji, które faktycznie wywołują wywołania dostawcy.

Uwaga: testy te należy uruchomić po wdrożeniu jako zautomatyzowane działanie po wdrożeniu. Nie są uruchamiane w ramach testów jednostkowych lub części procesu kompilacji.

Czasami test integracji jest lepszym rozwiązaniem niż test jednostkowy w niektórych częściach aplikacji. Obręcze, przez które należy przejść, aby kod był „testowalny”, mogą czasami być szkodliwe.

Jon Raynor
źródło
2
Pierwszą rzeczą, którą pomyślałem, kiedy przeczytałem twoją odpowiedź, było „nierealne w przypadku PDF, ponieważ porównywanie plików na poziomie binarnym nie mówi ci, co się zmieniło lub czy zmiana jest problematyczna”. Potem znalazłem ten starszy post SO , wskazuje, że może on faktycznie działać (więc +1).
Doc Brown
@DocBrown narzędzie w łączu SO nie porównuje plików PDF na poziomie binarnym. Porównuje strukturę i zawartość stron. Struktura wewnętrzna PDF: safaribooksonline.com/library/view/pdf-explained/9781449321581/...
linuxunil,
1
@linuxunil: tak, wiem, jak napisałem, najpierw pomyślałem ... ale potem znalazłem rozwiązanie wspomniane powyżej.
Doc Brown
och ... rozumiem ... może „to naprawdę może zadziałać” w końcowej części twojej odpowiedzi wprawiło mnie w zakłopotanie.
linuxunil,
1

Jak rozumiem, ta dyskusja koncentruje się na możliwościach automatyzacji tworzenia opakowań, a nie na pakowaniu pomysłów i wytycznych dotyczących wdrażania. Spróbuję oderwać się od pomysłu, ponieważ już tutaj jest wiele.

Widzę, że bawimy się technologiami .NET, dlatego mamy w swoich rękach potężne możliwości odbicia. Możesz rozważyć:

  1. Narzędzie takie jak .NET Wrapper Class Generator . Nie korzystałem z tego narzędzia i wiem, że działa ono na starszym stosie technologii, ale może w twoim przypadku będzie pasować. Oczywiście jakość kodu, wsparcie dla odwrócenia zależności i segregacji interfejsów należy zbadać osobno. Może istnieją inne takie narzędzia, ale nie szukałem dużo.
  2. Pisanie własnego narzędzia, które będzie wyszukiwało w odnośnym zespole metody publiczne / interfejsy oraz dokonuje mapowania i generowania kodu. Wkład w społeczność byłby więcej niż mile widziany!
  3. Jeśli .NET nie jest przypadkiem ... może zajrzyj tutaj .

Uważam, że taka automatyzacja może stanowić punkt bazowy, ale nie ostateczne rozwiązanie. Konieczne będzie ręczne refaktoryzowanie kodu ... lub w najgorszym przypadku przeprojektowanie, ponieważ całkowicie zgadzam się z tym, co napisał svidgen:

Zbuduj swoją aplikację w oparciu o pożądany interfejs; nie przeciwko interfejsowi API innej firmy.

tom3k
źródło
Ok, przynajmniej pomysł jak problem może być obsługiwane. Ale nie na podstawie własnego doświadczenia w tym przypadku użycia, zakładam?
Doc Brown
Pytanie opiera się na moich własnych doświadczeniach dotyczących dziedziny inżynierii oprogramowania (opakowanie, odbicie, di)! Jeśli chodzi o narzędzia - nie użyłem tego, jak podano w odpowiedzi.
tom3k,
0

Podczas tworzenia bibliotek opakowań należy przestrzegać następujących wskazówek:

  • ujawnij tylko niewielki podzbiór biblioteki innej firmy, której teraz potrzebujesz (i rozwijaj na żądanie)
  • utrzymuj opakowanie tak cienkie, jak to możliwe (nie ma tam logiki).
Rumen Georgiev
źródło
1
Zastanawiam się tylko, dlaczego otrzymałeś 3 głosy poparcia dla postu, który nie trafia w sedno pytania.
Doc Brown