Wann ist eine 'Kern'-Bibliothek eine schlechte Idee?

8

Bei der Entwicklung von Software habe ich häufig eine zentralisierte 'Kern'-Bibliothek mit handlichem Code, der von verschiedenen Projekten gemeinsam genutzt und referenziert werden kann.

Beispiele:

  • eine Reihe von Funktionen zum Bearbeiten von Zeichenfolgen
  • häufig verwendete reguläre Ausdrücke
  • allgemeiner Bereitstellungscode

Einige meiner Kollegen scheinen sich jedoch von diesem Ansatz abzuwenden. Sie haben Bedenken wie den Wartungsaufwand für das erneute Testen von Code, der von vielen Projekten verwendet wird, sobald ein Fehler behoben ist. Jetzt überlege ich, wann ich das tun soll.

Was sind die Probleme, die die Verwendung einer "Kern" -Bibliothek zu einer schlechten Idee machen?

Alex Angas
quelle
Eine Kernbibliothek ist eine gute Idee, wenn der Code häufig wiederverwendet wird, aber er muss religiös getestet werden, einschließlich Unit-Tests und anderer Weltraumtechnologien.
Job
Es ist eine gute Idee, wenn es sich stabilisiert hat und sich nicht ändert.
Martin York
Das Anliegen der erneuten Prüfung ist sehr berechtigt. Möchten Sie herausfinden, dass Sie vor 6 Monaten ein Wartungsprojekt abgebrochen haben?
Ich kann mir nicht vorstellen, meinen gesamten Dienstprogrammcode jedes Mal neu zu schreiben, wenn ich ihn brauchte.

Antworten:

12

Kernbibliotheken sind schlecht, wenn sie unter Feature Creep leiden, und sehr schlecht, wenn sie nicht gut gewartet werden.

Vielleicht finden Sie diesen Artikel für einen erweiterten Standpunkt interessant (dem ich voll und ganz zustimme):

http://www.yosefk.com/blog/redundancy-vs-dependencies-which-is-worse.html


Don Knuth: "Für mich ist 'wiederbearbeitbarer Code' viel, viel besser als eine unberührbare Black Box oder ein Toolkit ... Sie werden mich nie davon überzeugen, dass wiederverwendbarer Code keine Bedrohung darstellt."

Denis de Bernardy
quelle
3

Wenn Sie die Idee verwenden, dass eine Kernbibliothek schlecht ist, wenn mehrere Projekte davon abhängen, sollten Sie jQuery nicht für das Web, libxml in Ihren * nix-Apps oder einem anderen Framework oder einer anderen Bibliothek verwenden. Betrachten Sie das gesamte Ökosystem der modernen Entwicklung (DRY, OOP usw.) und jede einzelne App besteht aus einer Reihe von Bibliotheken und Frameworks.

Was schlecht sein kann, ist, wenn Sie keine Art von Unit-Tests haben, keinen Regressionstest und keine Art von API / ABI für Ihre Bibliothek verwenden. Wenn alle Ihre Anwendungen ordnungsgemäß getestet wurden, verfügt Ihre Bibliothek über ordnungsgemäße Tests, und Sie stellen sicher, dass Sie die API-Versionsnummer entsprechend aktualisieren, wenn Sie Funktionsaufrufe unterbrechen.

Für eine vollständige Abdeckung möchten Sie wahrscheinlich, wenn Änderungen an der Bibliothek vorgenommen werden, eine Reihe von Tests ausführen, die sicherstellen, dass die API nicht beschädigt wurde und dass die Ausführung des gesamten Codes fehlerfrei ist. Anschließend können Sie das neueste Bibliotheksupdate in Ihre Anwendung einfügen und dieselben Tests ausführen. Wenn Sie die API aktualisieren, sollte sie dokumentiert werden, damit Sie wissen, was Sie in Ihrer Anwendung tun müssen, um sie zu aktualisieren. In beiden Fällen können Sie beim Ausführen der Tests für Ihre Anwendung genauso sicher sein wie bei Ihren Tests, dass nichts kaputt gegangen ist.

Wenn Sie jquery, mootools oder eine andere Javascript-Bibliothek oder ein anderes Javascript-Framework verwenden, können Sie die neue Version nicht einfach blind verwenden. Leider können Sie dies manchmal nicht einmal mit einer kleineren Version 1.6.z tun.

Ryan Gibbons
quelle
3

Sie haben Bedenken wie den Wartungsaufwand für das erneute Testen von Code, der von vielen Projekten verwendet wird, sobald ein Fehler behoben ist.

Wenn Sie über umfassende Komponententests für die Kernbibliothek verfügen; das ist kein Problem. Es wird kein Code eingecheckt, es sei denn, alle Tests bestehen. Wenn Sie einen Fehler einführen, schreiben Sie einen Fehlertest, um den Fehler zu reproduzieren und zu beheben. Dann werden Sie auch immer auf diesen Fehler testen. Für immer.

Auch die von Ihnen beschriebene Funktionalität ist sehr einfach zu schreiben.

Als Nebenproblem möchten Sie möglicherweise mehr als eine Kernbibliothek haben, sodass Sie den RegEx-Code nicht einschließen müssen, es sei denn, Sie möchten.

mcottle
quelle
2

Ich werde dies etwas anders angehen. Eine Kernbibliothek ist in vielen Fällen eine hervorragende Idee!

Wenn Sie zwei separate Projekte haben, sollten sich diese in zwei separaten Code-Repositorys befinden. Jetzt hängen sie von der gemeinsamen Funktionalität ab. Betrachten wir zum Beispiel Paketverarbeitungsanwendungen. Die allgemeine Funktionalität kann umfassen:

  • Speicherzuordnungen
  • Adressauflösungsprotokoll
  • AVL-Baum
  • Serialisierungscode für binäre Protokolle
  • Dynamisches Array
  • Hash-Liste im Linux-Kernel-Stil mit einfach verknüpftem Kopf und doppelt verknüpften mittleren Knoten
  • Hash-tabelle
  • TCP / IP-Header-Verarbeitungscode
  • Regelmäßige verknüpfte Liste mit doppelt verknüpftem Kopf und doppelt verknüpften Mittelknoten
  • Protokollierungsbibliothek
  • Verschiedenes (vertrau mir, du brauchst das für kleine und triviale Dinge oder deine Anzahl an verschiedenen Modulen wird so groß wie 100 sein!)
  • Paketerfassungsbibliothek
  • Paket-E / A-Schnittstellenbibliothek
  • Paketdatenstruktur
  • Blockierwarteschlange für die Kommunikation zwischen Threads
  • Zufallszahlengeneratoren
  • Rot-schwarzer Baum
  • Eine Art Timer-Implementierung

Nun benötigen verschiedene Paketverarbeitungsanwendungen möglicherweise eine andere Teilmenge davon. Sollten Sie eine Kernbibliothek mit einem Quellcode-Repository implementieren oder sollten Sie für jedes dieser Module 18 verschiedene Repositorys haben? Denken Sie daran, dass diese Module möglicherweise gegenseitige Abhängigkeiten aufweisen, sodass die meisten dieser Module beispielsweise von verschiedenen Modulen abhängen können.

Ich werde behaupten, dass eine Kernbibliothek der beste Ansatz ist. Es reduziert den Overhead vieler Quellcode-Repositorys. Es reduziert die Abhängigkeitshölle: Eine bestimmte Version von Speicherzuweisern benötigt möglicherweise eine bestimmte Version eines anderen Moduls. Und was ist, wenn Sie Speicherzuweiser Version 1.7 abhängig von Verschiedenes 2.5 und AVL-Baum Version 1.2 abhängig von Verschiedenes 2.6 wollen? Möglicherweise können Sie nicht gleichzeitig verschiedene 2.5 und 2.6 mit Ihrem Programm verknüpfen.

Implementieren Sie also die folgende Struktur:

  • Kernbibliotheks-Repository
  • Projekt # 1 Repository
  • Projekt # 2 Repository
  • ...
  • Projekt # N Repository

Ich habe gesehen, dass der Wechsel von der Struktur zu dieser Art von Struktur:

  • Projekt # 1 Repository
  • Projekt # 2 Repository
  • ...
  • Projekt # N Repository

Hat zu weniger Wartung und mehr Code-Sharing über Nicht-Copypaste-Mechanismen geführt.

Ich habe auch Projekte mit der folgenden Struktur gesehen:

  • Speicherzuweisungs-Repository
  • Adressauflösungsprotokoll-Repository
  • AVL-Baum-Repository
  • Serialisierungscode für das Repository für binäre Protokolle
  • Dynamisches Array-Repository
  • Hash-Liste im Linux-Kernel-Stil mit einfach verknüpftem Kopf und doppelt verknüpftem Repository für mittlere Knoten
  • Hash-Tabellen-Repository
  • TCP / IP-Header-Verarbeitungscode-Repository
  • Regelmäßige verknüpfte Liste mit doppelt verknüpftem Kopf und doppelt verknüpftem Mittelknoten-Repository
  • Protokollierungsbibliotheks-Repository
  • Verschiedenes Repository (vertrau mir, du brauchst das für kleine und triviale Dinge oder deine Anzahl an verschiedenen Modulen wird so groß wie 100 sein!)
  • Repository der Paketerfassungsbibliothek
  • Repository der Paket-E / A-Schnittstellenbibliothek
  • Paketdatenstruktur-Repository
  • Blockierungswarteschlange für das Kommunikations-Repository zwischen Threads
  • Repository für Zufallszahlengeneratoren
  • Rot-Schwarz-Baum-Repository
  • Eine Art Timer-Implementierungs-Repository
  • Projekt # 1 Repository
  • Projekt # 2 Repository
  • ...
  • Projekt # N Repository

... und die Abhängigkeitshölle und die Verbreitung von Repository-Nummern waren echte Probleme.

Sollten Sie jetzt eine vorhandene Open Source-Bibliothek verwenden, anstatt Ihre eigene zu schreiben? Sie müssen berücksichtigen:

  • Lizenzprobleme. Manchmal kann die bloße Anforderung, dem Autor in der bereitgestellten Dokumentation eine Anerkennung zu geben, zu hoch sein, da 20 Bibliotheken normalerweise 20 verschiedene Autoren haben.
  • Unterstützung für verschiedene Betriebssystemversionen
  • Abhängigkeiten der jeweiligen Bibliothek
  • Größe der jeweiligen Bibliothek: Ist sie für die bereitgestellte Funktionalität zu groß? Bietet es zu viele Funktionen?
  • Ist eine statische Verknüpfung möglich? Ist eine dynamische Verknüpfung wünschenswert?
  • Ist die Schnittstelle der Bibliothek das, was Sie wollen? Beachten Sie, dass das Schreiben eines Wrappers zur Bereitstellung der gewünschten Schnittstelle in einigen Fällen einfacher sein kann als das Umschreiben der gesamten Komponente selbst.
  • ... und viele, viele andere Dinge, die ich in dieser Liste nicht erwähnt habe

Normalerweise verwende ich die Regel, dass alles unter 1000 Codezeilen, was nicht etwas erfordert, das über das Fachwissen des Programmierers hinausgeht, selbst implementiert werden sollte. Hinweis: Die 1000 Zeilen enthalten Unit-Tests. Daher würde ich es auf keinen Fall empfehlen, 1000 Codezeilen selbst zu schreiben, wenn 10 000 zusätzliche Zeilen für Komponententests erforderlich sind. Für meine Paketverarbeitungsprogramme bedeutet dies, dass ich nur folgende externe Komponenten verwendet habe:

  • Alles, was von einer Standard-Linux-Distribution bereitgestellt wird, da es so viele Codezeilen gibt, dass es keinen Sinn macht, Linux neu zu implementieren. Teile der Neuimplementierung von Linux würden auch über mein Fachwissen hinausgehen.
  • Bison / Flex, weil das LALR-Parsen über mein Fachwissen und über 1000 Codezeilen hinausgeht. Ich könnte sicherlich selbst einen rekursiven Abstiegsparser schreiben, aber Bison / Flex sind so praktisch, dass ich sie als nützlich betrachte.
  • Netmap, weil es über 1000 Zeilen sind und über mein Fachwissen hinausgehen
  • Auf Überspringen von Listen basierende Timer-Implementierung von DPDK, da sie über mein Fachwissen hinausgeht, obwohl sie weniger als 1000 Codezeilen umfasst (obwohl ich alternative Timer-Implementierungen habe, die keine Überspringlisten verwenden)

Einige Dinge, die ich selbst implementiert habe, weil sie einfach sind, umfassen sogar Dinge wie:

  • MurMurHash
  • SipHash
  • Mersenne Twister

... weil benutzerdefinierte Implementierungen davon starkes Inlining ermöglichen können, was zu einer verbesserten Leistung führt.

Ich mache keine Kryptographie; Wenn ich das tun würde, würde ich der Liste eine Art Kryptobibliothek hinzufügen, da das Schreiben von Kryptoalgorithmen für sich selbst anfällig für Cache-Timing-Angriffe sein kann, selbst wenn Sie durch gründliche Unit-Tests zeigen können, dass sie mit den offiziellen Algorithmen kompatibel sind.

juhist
quelle
1

Eine Kernbibliothek kann schlecht sein, wenn mehrere Projekte davon abhängen. Sie müssen nicht nur Änderungen an Ihrem Kern testen, sondern auch jedes einzelne abhängige Projekt einem Regressionstest unterziehen. Zweitens können sich Ihre Kern-APIs niemals ändern, da Sie jedes abhängige Projekt neu gestalten müssen. Je mehr Projekte Ihre Bibliothek verwenden, desto tiefer ist die Falle.

Ein weiteres Problem ist die Tendenz, alles "Gemeinsame" in Ihre Kernbibliothek zu werfen, es aufzublähen und es schwieriger zu machen, nach kleinen Stücken zu suchen. Ich sage nur, dass ich einmal von einem Ort gehört habe, der Angst hatte, eine der zahlreichen Kernbibliotheken zu berühren, der Aufwand für QS-Regressionstests so groß war.

Vielleicht können Sie stattdessen eine Code-Snippet-Ressource erstellen, mit der Projektteams den benötigten Code suchen und abrufen und sich von Wartungs- oder Regressionsproblemen trennen können? Das mache ich sowieso zu Hause.

Patrick Hughes
quelle
4
Es ist viel schwieriger, einen Fehler in Codefragmenten zu beheben, die kopiert und an mehreren Stellen eingefügt wurden, nicht wahr?
Alex Angas
Ein Zitat von Donald Knuth: "Ich muss auch gestehen, dass die Mode für wiederverwendbaren Code stark voreingenommen ist. Für mich ist" wiederbearbeitbarer Code "viel, viel besser als eine unberührbare Black Box oder ein Toolkit. Ich könnte weiter und weiter machen Wenn Sie völlig davon überzeugt sind, dass wiederverwendbarer Code wunderbar ist, werde ich Sie wahrscheinlich sowieso nicht beeinflussen können, aber Sie werden mich nie davon überzeugen, dass wiederverwendbarer Code keine Bedrohung darstellt. "
Patrick Hughes
@AlexAngas: Das ist wahr, aber es kann Fälle geben , in denen eine Bibliothek Buggy, aber korrekt funktioniert nur , weil einige andere Bibliothek subtile Bugs , die die Fehler in der ersten versetzt. Während beide Fehlergruppen nach Möglichkeit behoben werden sollten, würde eine Kopie des Quellcodes der zweiten Bibliothek als Teil des Projekts mit der ersten bedeuten, dass eine angewendete Fehlerbehebung für diesen Code eine erkennbare Änderung des Projekts darstellt könnte vorübergehend zurückgesetzt werden, wenn es kaputt geht (wodurch es als Ursache für den Bruch identifiziert werden kann).
Supercat
@AlexAngas: Natürlich bedeutet das Identifizieren des Fixes für die zweite Routine als Ursache des Bruchs nicht, dass das Problem nicht darin besteht, das zweite zu reparieren, sondern weist darauf hin, dass sich ein Code fälschlicherweise auf das fehlerhafte Verhalten dieser Routine stützt ;; Diese Entdeckung wird der Schlüssel zur effizienten Lösung der tatsächlichen Probleme sein. Wenn man dagegen nur weiß, dass der Code, der früher spontan funktioniert hat, nicht mehr funktioniert, ist es sehr schwierig, herauszufinden, was man dagegen tun kann.
Supercat
1

Ein Punkt, der noch nicht erwähnt wurde, ist, dass jeder Code von etwas abhängig sein wird , selbst wenn es buchstäblich das einzige ist, was im ROM eines eingebetteten Mikrocontrollers ausgeführt wird. Wenn der Hersteller des Controllers ein Verhalten ändert, auf das sich der Code stützt, muss der Code entweder geändert werden, um mit Chips zu arbeiten, die nach der Änderung hergestellt wurden, oder Hersteller des Geräts, das den Code verwendet, müssen irgendwie Chips erwerben, die dies tun die Änderung nicht einbeziehen - möglicherweise eine Preisprämie für sie zahlen.

Die Verwendung einer Bibliothek zum Ausführen verschiedener Hardwarefunktionen kann bedeuten, dass Code jetzt von einer Bibliothek abhängig ist, obwohl dies zuvor nicht der Fall war, aber es kann auch Abhängigkeiten zwischen dem Code und der Hardware beseitigen. Beispielsweise könnte ein Chiphersteller versprechen, eine Bibliothek für alle gegenwärtigen und zukünftigen Chips bereitzustellen, die immer bestimmte E / A-Funktionen auf eine bestimmte Weise ausführen. Code, der diese Bibliothek zum Ausführen dieser E / A-Funktionen verwendet, würde vom Hersteller abhängig, um geeignete Versionen dieser Bibliothek bereitzustellen, wäre jedoch nicht länger vom Hersteller abhängig, um dieselbe Hardware-Implementierung dieser Funktionen zu verwenden.

Leider ist es oft schwer zu wissen, welcher Ansatz für zukunftssicheren Code richtig ist. Ich habe Fälle gesehen, in denen ein Chiphersteller die Funktionsweise einer Bibliothek geändert hat (um neue Chips aufzunehmen), selbst wenn sie für den Zugriff auf einen geänderten Chip verwendet wurde. Ich habe auch Fälle gesehen, in denen ein Chiphersteller die Funktionsweise seiner Hardware geändert hat, die bereitgestellten Bibliotheken jedoch entsprechend angepasst wurden, sodass Code, der Bibliotheksroutinen verwendet, weiterhin unverändert funktioniert, während Code, der direkt auf Hardware zugreift, angepasst werden musste.

Ähnliche Situationen bestehen bei Windows-Anwendungen. Microsoft liebt es manchmal, die Art und Weise zu ändern, in der Anwendungen ausgeführt werden müssen. Code, der bestimmte Bibliotheken für solche Dinge verwendet, kann einfach durch Aktualisieren der Bibliothek aktualisiert werden, während Code, der keine Bibliotheken verwendet, die für sie aktualisiert werden, manuell aktualisiert werden muss.

Superkatze
quelle
1

Ich wollte mich mit einer etwas anderen Einstellung dazu einmischen, obwohl ich die Denis de BernardyAntwort und den verknüpften Artikel über das Minimieren von Abhängigkeiten im Vergleich zum Minimieren von Redundanzen liebe (sie spiegeln meine eigenen Gedanken zu diesem Thema wider, bei dem ich glaube, dass die Wiederverwendung von Code ein Balanceakt ist).

Das größte Problem, das ich mit einer coreBibliothek habe, ist folgendes:

Wann ist es fertig? Wann wird es einen Punkt der Stabilität erreichen, an dem es alles tun wird, was es tun muss, und effektiv "getan" werden kann?

Und ich denke, es ist sehr wahrscheinlich, dass die Antwort " nie " sein könnte. Die Leute könnten immer versucht sein, etwas hinzuzufügen, da es eine solch nebulöse Idee modelliert, insbesondere wenn sich diese Bibliothek nur während der Entwicklung der Software entwickelt, anstatt im Voraus mit Spannung erwartete Ziele zu haben. Und vielleicht ist das Hinzufügen zur Bibliothek nicht das Schlimmste auf der Welt, da es bestehende Abhängigkeiten von der Bibliothek nicht aufhebt. Angesichts dieser nebulösen Ziele könnte die Bibliothek jedoch zunehmend vielseitiger und hässlicher werden und unterschiedliche Funktionen bieten, an denen sich jemand interessiert Bei Verwendung der Bibliothek wird möglicherweise nur ein kleiner Teil davon gefunden, der für ihre Anforderungen geeignet ist.

Die Abhängigkeiten in Ihrer Codebasis sollten idealerweise in Richtung sehr stabiler Pakete fließen. Ein corePaket kann leicht sehr instabil werden, während große Teile Ihrer Codebasis Abhängigkeiten aufweisen.

Daher denke ich, dass es sich lohnt, die Bibliothek in einheitlichere Bibliotheken aufzuteilen, die sich etwas Spezifischerem widmen als nur " Kernbibliothek mit allem, was Menschen häufig benötigen", damit sie mit einer besseren Koordination zwischen Ihren Teamkollegen in eine einheitlichere Richtung wachsen kann über genau das, was es tun sollte und, was noch wichtiger ist, nicht tun sollte, und möglicherweise einen Punkt der Stabilität erreichen, an dem es gut getestet ist und Sie nicht das Gefühl haben, dass noch etwas hinzugefügt werden muss, damit es relativ ist. " komplett "und stabil (wie in, unveränderlich).


quelle
0

Das Schreiben von Bibliotheken für grundlegende Dinge wie Zeichenfolgen und verknüpfte Listen ist in diesem Jahrtausend ziemlich dumm. Verwenden Sie eine im Lieferumfang der Batterien enthaltene Programmiersprache, in der die Kernfunktionalität bereits enthalten ist.

Wenn Sie nur zum Spaß Kernbibliotheken zur Laufzeitunterstützung schreiben möchten, entwerfen Sie eine neue Programmiersprache. Wenn Sie dies in einer Anwendung tun, entwickeln Sie im Wesentlichen eine Sprache aus ihrer Seite heraus.

Hat nicht schon jemand N verschiedene Kernbibliotheken in der von Ihnen verwendeten Sprache geschrieben? Das Erforschen vorhandener Frameworks und das Auswählen des am besten geeigneten Frameworks kann die Zeit besser nutzen als von Grund auf neu.

Kaz
quelle
In meinem Bereich ist eine leistungsstarke Paketverarbeitung, die sicherlich eine mit Batterien enthaltene Programmiersprache verwendet, keine Option. C ist die offensichtliche Wahl. Und nein, die N verschiedenen Kernbibliotheken, die beispielsweise für Hash-Tabellen verfügbar sind, sind schlechter als die Linux-Kernel-Implementierung. Da die Linux-Kernel-Implementierung GPL-fähig ist, müssen Sie eine ähnliche Implementierung manuell selbst implementieren, ohne den Linux-Kernel-Quellcode zu betrachten. Wenn Sie jedoch die erweiterten Hash-Tabellenfunktionen kennen, die die Linux-Kernel-Implementierung verwendet. Dies kann jedoch auf dem Feld variieren.
Juhist