Ich habe in letzter Zeit immer mehr Probleme gesehen, die denen ähneln, die in diesem Artikel über Feature-Schnittpunkte erläutert wurden . Ein anderer Begriff dafür wären Produktlinien, obwohl ich diese eher verschiedenen Produkten zuschreibe, während ich diese Probleme normalerweise in Form möglicher Produktkonfigurationen habe.
Die Grundidee dieser Art von Problem ist einfach: Sie fügen einem Produkt eine Funktion hinzu, aber aufgrund einer Kombination anderer vorhandener Funktionen werden die Dinge irgendwie kompliziert. Schließlich stellt die Qualitätssicherung ein Problem mit einer seltenen Kombination von Funktionen fest, an die noch niemand gedacht hat, und was ein einfacher Bugfix gewesen sein sollte, kann sogar dazu führen, dass größere Designänderungen erforderlich werden.
Die Dimensionen dieses Merkmalskreuzungsproblems sind von überwältigender Komplexität. Angenommen, die aktuelle Softwareversion verfügt über N
Funktionen, und Sie fügen eine neue Funktion hinzu. Vereinfachen wir die Dinge auch, indem wir sagen, dass jedes der Features nur ein- oder ausgeschaltet werden kann. Dann müssen Sie bereits 2^(N+1)
mögliche Feature-Kombinationen berücksichtigen. Aufgrund des Mangels an besseren Formulierungen / Suchbegriffen bezeichne ich die Existenz dieser Kombinationen als Merkmalskreuzungsproblem . (Bonuspunkte für eine Antwort einschließlich Referenz (en) für eine festere Laufzeit.)
Die Frage, mit der ich zu kämpfen habe, ist nun, wie ich mit diesem Komplexitätsproblem auf jeder Ebene des Entwicklungsprozesses umgehen kann. Aus offensichtlichen Kostengründen ist es bis zur Utopie unpraktisch, jede Kombination einzeln ansprechen zu wollen. Schließlich versuchen wir aus gutem Grund, uns von exponentiellen Komplexitätsalgorithmen fernzuhalten, aber den Entwicklungsprozess selbst in ein Monster mit exponentieller Größe zu verwandeln, führt zwangsläufig zu einem völligen Misserfolg.
Wie können Sie also systematisch das beste Ergebnis erzielen, das keine Budgets sprengt und auf anständige, nützliche und professionell akzeptable Weise vollständig ist?
Spezifikation: Wenn Sie eine neue Funktion angeben, wie stellen Sie sicher, dass sie mit allen anderen Kindern gut funktioniert?
Ich kann sehen, dass man jedes vorhandene Feature in Kombination mit dem neuen Feature systematisch untersuchen könnte - aber das wäre isoliert von den anderen Features. Angesichts der Komplexität einiger Merkmale ist diese isolierte Ansicht häufig bereits so involviert, dass ein strukturierter Ansatz an sich erforderlich ist, geschweige denn der
2^(N-1)
Faktor, der durch die anderen Merkmale verursacht wird, die man bereitwillig ignoriert.Implementierung: Wenn Sie eine Funktion implementieren, wie stellen Sie sicher, dass Ihr Code in allen Fällen ordnungsgemäß interagiert / sich überschneidet.
Wieder wundere ich mich über die schiere Komplexität. Ich kenne verschiedene Techniken, um das Fehlerpotential von zwei sich überschneidenden Merkmalen zu verringern, aber keine, die sich auf vernünftige Weise skalieren lassen. Ich gehe jedoch davon aus, dass eine gute Strategie während der Spezifikation das Problem während der Implementierung in Schach halten sollte.
Überprüfung: Wenn Sie ein Feature testen - wie gehen Sie damit um, dass Sie nur einen Bruchteil dieses Feature-Schnittraums testen können?
Es ist schwer genug zu wissen, dass das isolierte Testen eines einzelnen Features nichts annähernd fehlerfreien Codes garantiert. Wenn Sie dies jedoch auf einen Bruchteil reduzieren,
2^-N
scheinen Hunderte von Tests nicht einmal einen einzigen Wassertropfen in allen Ozeanen zusammen abzudecken . Schlimmer noch, die problematischsten Fehler sind diejenigen, die sich aus der Schnittmenge von Merkmalen ergeben, von denen man nicht erwarten kann, dass sie zu Problemen führen - aber wie testen Sie diese, wenn Sie keine so starke Schnittmenge erwarten?
Während ich gerne hören möchte, wie andere mit diesem Problem umgehen, interessiere ich mich hauptsächlich für Literatur oder Artikel, die das Thema eingehender analysieren. Wenn Sie also persönlich einer bestimmten Strategie folgen, wäre es schön, entsprechende Quellen in Ihre Antwort aufzunehmen.
quelle
Antworten:
Wir wussten bereits mathematisch, dass die Überprüfung eines Programms im allgemeinsten Fall aufgrund des Halteproblems in endlicher Zeit unmöglich ist. Diese Art von Problem ist also nicht neu.
In der Praxis kann ein gutes Design eine Entkopplung bewirken, so dass die Anzahl der sich überschneidenden Merkmale weit unter 2 ^ N liegt, obwohl sie selbst in gut entworfenen Systemen sicherlich über N zu liegen scheint.
Was Quellen angeht, scheint es mir, dass fast jedes Buch oder Blog über Software-Design effektiv versucht, diese 2 ^ N so weit wie möglich zu reduzieren, obwohl ich keine kenne, die das Problem in den gleichen Begriffen wie Sie ansprechen machen.
Als Beispiel dafür, wie Design dabei helfen könnte, wurde in dem erwähnten Artikel ein Teil der Feature-Überschneidungen aufgetreten, weil sowohl Replikation als auch Indizierung vom eTag ausgelöst wurden. Wenn sie einen anderen Kommunikationskanal zur Verfügung gehabt hätten, um die Notwendigkeit für jeden dieser Kanäle separat zu signalisieren, hätten sie möglicherweise die Reihenfolge der Ereignisse einfacher steuern können und weniger Probleme gehabt.
Oder vielleicht nicht. Ich weiß nichts über RavenDB. Die Architektur kann Feature-Schnittpunktprobleme nicht verhindern, wenn die Features wirklich unerklärlich miteinander verflochten sind, und wir können nie im Voraus wissen, dass wir kein Feature wollen, das wirklich den schlimmsten Fall einer 2 ^ N-Kreuzung aufweist. Die Architektur kann jedoch Schnittpunkte aufgrund von Implementierungsproblemen zumindest einschränken.
Auch wenn ich mich bei RavenDB und eTags irre (und ich benutze es nur aus Gründen der Argumentation - sie sind kluge Leute und haben es wahrscheinlich richtig verstanden), sollte klar sein, wie Architektur helfen kann . Die meisten Muster, über die gesprochen wird, wurden explizit mit dem Ziel entwickelt, die Anzahl der Codeänderungen zu verringern, die für neue oder sich ändernde Funktionen erforderlich sind. Dies geht weit zurück - zum Beispiel "Entwurfsmuster, Elemente wiederverwendbarer objektorientierter Software", heißt es in der Einleitung "Jedes Entwurfsmuster lässt einige Aspekte der Architektur unabhängig von anderen Aspekten variieren, wodurch ein System für eine bestimmte Art robuster wird Veränderung".
Mein Punkt ist, man kann ein Gefühl für das große O von Feature-Schnittpunkten in der Praxis bekommen, indem man sich anschaut, was in der Praxis passiert. Bei der Untersuchung dieser Antwort stellte ich fest, dass die meisten Analysen der Funktionspunkte / des Entwicklungsaufwands (dh der Produktivität) entweder weniger als das lineare Wachstum des Projektaufwands pro Funktionspunkt oder nur geringfügig über dem linearen Wachstum ergaben. Was ich etwas überraschend fand. Dies hatte ein ziemlich lesbares Beispiel.
Diese (und ähnliche Studien, von denen einige Funktionspunkte anstelle von Codezeilen verwenden) beweisen nicht, dass keine Feature-Schnittmenge auftritt und Probleme verursacht, aber es scheint ein vernünftiger Beweis dafür zu sein, dass dies in der Praxis nicht verheerend ist.
quelle
Dies wird keineswegs die beste Antwort sein, aber ich habe über einige Dinge nachgedacht, die sich mit Punkten in Ihrer Frage überschneiden, und dachte, ich würde sie erwähnen:
Strukturelle Unterstützung
Nach dem Wenigen, das ich gesehen habe, ist es, wenn Funktionen fehlerhaft sind und / oder nicht gut mit anderen zusammenpassen, hauptsächlich auf die schlechte Unterstützung durch die Kernstruktur / das Framework des Programms für deren Verwaltung / Koordination zurückzuführen. Wenn Sie mehr Zeit damit verbringen, den Kern zu verfeinern und abzurunden, sollte dies meiner Meinung nach das Hinzufügen neuer Funktionen erleichtern.
Eine Sache , ich habe festgestellt , in den Anwendungen gemeinsam sein , wo ich Arbeit ist , dass die Struktur eines Programms wurde Griff einzurichten einer einer Art von Objekt oder Prozess , aber viele der Erweiterungen wir getan haben oder wollen zu tun haben mit dem Umgang mit vielen von einer Art zu tun . Wenn dies zu Beginn des Anwendungsdesigns stärker berücksichtigt worden wäre, hätte es später geholfen, diese Funktionen hinzuzufügen.
Dies wird sehr wichtig, wenn Unterstützung für mehrere X hinzugefügt wird, die Thread- / asynchronen / ereignisgesteuerten Code beinhalten, da diese Dinge ziemlich schnell schlecht werden können - ich hatte die Freude, eine Reihe von Problemen im Zusammenhang damit zu debuggen.
Es ist jedoch wahrscheinlich schwierig, diese Art von Aufwand im Vorfeld zu rechtfertigen, insbesondere für Prototypen oder einmalige Projekte - auch wenn einige dieser Prototypen oder einmaligen Projekte weiterhin verwendet werden oder als (Grundlage) des endgültigen Systems dienen. Das heißt, die Ausgaben hätten sich auf lange Sicht gelohnt.
Design
Wenn Sie beim Entwerfen des Kerns eines Programms mit einem Top-Down-Ansatz beginnen, können Sie die Dinge in überschaubare Teile verwandeln und sich mit der Problemdomäne befassen. Danach denke ich, dass ein Bottom-up-Ansatz verwendet werden sollte - dies wird dazu beitragen, die Dinge kleiner, flexibler und besser zu machen, um sie später zu erweitern. (Wie im Link erwähnt, führt eine solche Vorgehensweise zu kleineren Implementierungen der Funktionen, was weniger Konflikte / Fehler bedeutet.)
Wenn Sie sich auf die Kernbausteine des Systems konzentrieren und sicherstellen, dass alle gut interagieren, verhält sich alles, was mit ihnen erstellt wurde, wahrscheinlich auch gut und sollte sich besser in den Rest des Systems integrieren lassen.
Wenn ein neues Feature hinzugefügt wird, könnte beim Entwerfen ein ähnlicher Weg eingeschlagen werden wie beim Entwerfen des restlichen Frameworks: Zerlegen und dann von unten nach oben. Wenn Sie einen der ursprünglichen Blöcke aus dem Framework bei der Implementierung der Funktion wiederverwenden können, ist dies auf jeden Fall hilfreich. Sobald Sie fertig sind, können Sie alle neuen Blöcke, die Sie von der Funktion erhalten, zu den bereits im Kernframework enthaltenen hinzufügen und sie mit dem ursprünglichen Satz von Blöcken testen. Auf diese Weise sind sie mit dem Rest des Systems kompatibel und können in Zukunft verwendet werden Funktionen auch.
Vereinfachen!
Ich habe in letzter Zeit eine minimalistische Haltung zum Design eingenommen, angefangen mit der Vereinfachung des Problems bis hin zur Vereinfachung der Lösung. Wenn Zeit für eine Sekunde benötigt wird, um die Entwurfsiteration für ein Projekt zu vereinfachen, könnte ich sehen, dass dies sehr hilfreich ist, wenn später Dinge hinzugefügt werden.
Jedenfalls ist das mein 2c.
quelle