Laden...

Wie gemeinsames Fenster über Module teilen?

Erstellt von Raketenmaulwurf vor 4 Jahren Letzter Beitrag vor 4 Jahren 2.074 Views
Hinweis von Abt vor 4 Jahren

Abgeteilt von Fensterhandle erstellen
Es macht kein Sinn ein 11 Jahre altes Thema aus der Versenkung zu holen.

R
Raketenmaulwurf Themenstarter:in
5 Beiträge seit 2019
vor 4 Jahren
Wie gemeinsames Fenster über Module teilen?

Moin!
Ich grabe mal dieses Thema aus, da ich praktisch das gleiche Problem habe und leider auch nach stundenlanger Suche nicht zum Ziel komme.

Folgende Ausgangssituation:
Ich entwickle ein modulares Programm als Forms Anwendung. Die zugehörigen Module (Konsolenanwendungen) binde ich dynamisch bei Programmstart als dll Dateien ein. Jetzt habe ich den Auftrag bekommen, ein universelles Ausgabefenster zu erschaffen, welches zu jeder Zeit von jedem beliebigen Modul Informationen bekommen und ausgeben soll. Solange ich das Ausgabefenster wie die Module als Konsolenanwendung erstelle funktioniert der Testaufbau mit einem einfachen Label auch perfekt. Jetzt würde ich das ganze aber gerne auch als Forms-Anwendung umsetzen und den Text in einer Listbox oder ListView darstellen. Ziel ist es, einzelne Meldungen auch wegklicken und filtern zu können, daher ist ein Label nicht zielführend.

Ich habe dazu einen Testaufbau programmiert, bestehend aus dem Hauptprogramm, zwei Modulen und dem Ausgabefenster:

Ausschnitt Hauptprogramm:

 
public MainForm()
		{
			InitializeComponent();
			Ausgabe 				= Assembly.LoadFile(@Pfad+"\\Module\\Ausgabefenster_Designstudie.dll");
			Modul 					= Assembly.LoadFile(@Pfad+"\\Module\\Interface_Test_Modul.dll");
			Modul2 					= Assembly.LoadFile(@Pfad+"\\Module\\Interface_Test_Modul2.dll");
			AusgabeObjekt 			= Ausgabe.CreateInstance("Ausgabefenster_Designstudie.MainForm");
			ModulObjekt 			= Modul.CreateInstance("Interface_Test_Modul.Program");
			ModulObjekt2 			= Modul2.CreateInstance("Interface_Test_Modul.Program");
			InfoAusgabe				= AusgabeObjekt.GetType().GetMethod("Aufruf");
			InfoModul				= ModulObjekt.GetType().GetMethod("Programm");
			InfoModul2				= ModulObjekt2.GetType().GetMethod("Programm");
		}
void ModulStartClick(object sender, EventArgs e)
		{
			Thread ModulThread 	= new Thread(new ThreadStart(ThreadModul));
			ModulThread.Start();
		}
public static void ThreadModul2()
		{
			InfoModul2.Invoke(ModulObjekt2, new Object[]{AusgabeObjekt});
		}

Mein Ziel bei den Modulaufrufen ist, jedem Modul das selbe Objekt des Ausgabefensters mit zu übergeben, sodass auch jedes Modul wirklich in das selbe Fenster schreibt und nicht jedes Modul ein eigenes öffnet.

Die Module sind recht unspektakulär (Ausschnitt):


string Text = "Ich bin der Text des Moduls 2";
MethodInfo InfoAusgabe = Ausgabe.GetType().GetMethod("Ausgeben");
InfoAusgabe.Invoke(Ausgabe, new Object[]{Text});

Das Ausgabefenster als Forms Anwendung:


public partial class MainForm : Form
	{
		MainForm Fenster;
		
		public static string Eintrag;
		
		public MainForm()
		{
			InitializeComponent();
		}
		public void Aufruf()
		{
			Fenster = new MainForm();
			Fenster.Meldeliste.Items.Add("Testeintrag");
			Fenster.Meldeliste.Refresh();
			Fenster.InitializeComponent();
			Fenster.ShowDialog();
		}
		public void Ausgeben(string Inhalt)
		{
			Eintrag = Inhalt;
			ThreadPool.QueueUserWorkItem(Eintragen);
		}
		private void Eintragen(object dummy)
		{
			MethodInvoker Nachtragen = delegate
			{
				Fenster.Meldeliste.Items.Add(Eintrag);
			};
			Invoke(Nachtragen);
		}
	}

Der Aufruf des Ausgabefensters aus dem Hauptprogramm funktioniert und der erste Testeintrag wird ebenfalls gleich in der Methode "Aufruf" mit erzeugt. Wenn ich aber versuche aus einem Modul heraus etwas in die ListBox einzutragen bekomme ich jedes Mal wieder die Meldung, dass das Fenster-Handle fehlt.

Ich habe bereits versucht mit Fenster.CreateHandle(); bzw. auch mit Fenster.CreateControl(); den Handler manuell zu erzeugen. Leider ohne Erfolg.

Wo liegt mein Denkfehler?

Danke schonmal für's geduldige Lesen 😉

5.657 Beiträge seit 2006
vor 4 Jahren

Ich entwickle ein modulares Programm als Forms Anwendung.

Dann solltest du dir als erstes mal Gedanken über die Software-Architektur machen. Warum ein Modul direkt auf ein Element des Fensters vom Hauptprogramm zugreifen muß, und dann Handles dafür benötigt werden und Reflection, erschließt sich mir überhaupt nicht.

Hier ein paar Links dazu:
[Artikel] Drei-Schichten-Architektur
[FAQ] Eigene Anwendung pluginfähig machen
[FAQ] Eigenen Event definieren / Information zu Events (Ereignis/Ereignisse)

Weeks of programming can save you hours of planning

R
Raketenmaulwurf Themenstarter:in
5 Beiträge seit 2019
vor 4 Jahren

Das Problem dabei sind die Vorgaben, die ich von "oben" bekomme:

  • Neue Module sollen hinzugefügt werden können, ohne den Quellcode des Hauptprogramms ändern zu können (daher Reflection; hat sich dafür bewährt und ist relativ simpel umzusetzen)

  • Einstellungen für die Module sollen schon vor der Ausführung im Hauptprogramm vorgenommen werden können ( hier verwende ich eine Mischung aus Reflection und XML-Konfigurationsdateien )

  • Globale Ausgabe aller Module in einem zentralen Fenster; So gestaltet, dass Meldungen gefiltert und als "erledigt" markiert werden können.

Dazu sind alle Module mit standardisierten Methoden ausgestattet, so dass zum Beispiel die verfügbaren Einstellmöglichkeiten jedes Moduls abgefragt werden können. Danach kann jedes Modul über die "Progamm" Methode ausgeführt werden. Als Rückgabewert gibt es eine Liste von strings die praktisch die Meldungen des Moduls darstellen. Diese Meldungen wurden bisher im Hauptprogramm in ein TabControl geladen und angezeigt. Es konnte dabei jedes Modul einen eigenen Reiter bekommen, um die Informationen zu filtern.

Diese Variante stieß bei meinen Kollegen aber auf wenig Zustimmung, weil es keine Echtzeit-Rückmeldung darstellt und auch für eine modulübergreifende Filterung der Meldungen ungeeignet ist. Also muss jetzt ein neues Ausgabefenster her. Und würde ich es als extra Modul in Form einer Konsolenanwendung programmieren, wäre auch schon alles gelaufen. Aber der Aufwand, ohne Designer die kompletten Controls einzuprogrammieren ist einfach enorm, daher würde ich es gerne als Forms Anwendung entwerfen und auch als PlugIn verwenden.

Was genau dieser Fenster-Handle tut ist mir auch nicht hundertprozentig klar. So tief stehe ich jetzt nicht drin in der Materie. Ich würde nur gerne das Ausgabefenster als Modul einbinden und zwar so, dass es jederzeit von allen anderen Modulen mit Nachrichten gefüttert werden kann. Ich habe leider auch nicht die Zeit, das ganze Projekt neu aufzuziehen und ein anderes PlugIn System einzubinden.

Von daher wäre ich dankbar für den ein oder anderen Tipp wie sich das so umsetzen lässt.

16.807 Beiträge seit 2008
vor 4 Jahren

Das Problem dabei sind die Vorgaben, die ich von "oben" bekomme:

Allgemein ausgedrückt: wenn die "Oben" keine Ahnung haben, dann ist Deine Aufgabe hier auch mal "Nein" zu sagen, dass der Wunsch nach einer Eierlegenden-Wollmilchsau bullshit ist.

Das, was Du hier aber beschreibst, sollte problemlos durch ein ordentliches Pluginsystem machbar sein.
Dazu muss man aber nirgends nen Fenster-Handle mitgeben - sondern ne saubere Architektur aufbauen.

5.657 Beiträge seit 2006
vor 4 Jahren

Die Vorgaben "von Oben" scheinen mir jetzt nicht besonders unsinnig zu sein. Die Umsetzung aber schon.

Lies dir doch erstmal die verlinkten Artikel (oder andere Artikel) durch. Du bist nicht der erste Entwickler, der ein Program mit Plugins (oder Add-Ons oder "Modulen") erstellen soll.

Wenn das ganze auf Interfaces basiert, dann kann man alles wunderbar mit OOP umsetzen, benötigt keine Reflection, keine Fensterhandles, hat gut lesbaren und weiterzuentwickelnden Code, und bekommt ordentliche Kompilermeldungen, wenn man irgendwo einen Fehler macht.

Weeks of programming can save you hours of planning

R
Raketenmaulwurf Themenstarter:in
5 Beiträge seit 2019
vor 4 Jahren

Ich habe ja inzwischen mitbekommen, dass mein Ansatz nicht gerade elegant oder besonders formschön ist. Als ich vor knapp anderthalb Jahren dieses Projekt bekommen habe ging es nur um ein Hauptfenster zur Verwaltung der Module und die einzelnen Aufgaben, gekapselt jede für sich in einem separaten Modul. Inzwischen habe ich mehrere 10.000 Codezeilen in mehreren Module und dem Hauptprogramm geschrieben und es hat immer prima funktioniert.

Jetzt aus der kalten heraus kommt die Anforderung, noch das Ausgabefenster als eigenständiges Modul mit hinzuzufügen, weil meine Kollegen eben das Hauptfenster irgendwo im Hintergrund haben wollen und sich voll auf das Ausgabefenster konzentrieren. Außerdem steht so einfach mehr Platz zur Verfügung.

Wie gesagt ich habe die ganze Mimik schon erfolgreich zum laufen gebracht, indem ich das Modul für das Ausgabefenster als Konsolenanwendung geschrieben habe und IN diesem Modul eine Forms-Klasse eingefügt habe:


namespace Interface_Test_Ausgabe
{
	class Program
	{
		static Fenster Ausgabe;
		
		public static string Eingabe = null;
		
		[STAThread]
		public static void Main(string[] args)
		{
			Programm();
		}
		
		public static void Programm()
		{
			Ausgabe = new Fenster();
			Application.Run(Ausgabe);
		}
		
		public static void Ausgeben(string Text)
		{
			Eingabe = Text;
			Ausgabe.Starten();
		}
		
		public class Fenster : Form
		{
			public Label Textfeld1 		= new Label();
			
			public Fenster()
			{
				Textfeld1.Location 		= new Point(12,12);
				Textfeld1.Size			= new Size(260,200);
				Textfeld1.Text			= string.Empty;
				this.Size				= new Size(300,300);
				this.StartPosition		= FormStartPosition.CenterScreen;
				this.MinimizeBox		= false;
				this.MaximizeBox		= false;
				this.Controls.Add(Textfeld1);
			}
			public void Starten()
			{
				ThreadPool.QueueUserWorkItem(Eintragen);
			}
			private void Eintragen(object dummy)
			{
				MethodInvoker Nachtragen = delegate
				{
					Textfeld1.Text = Textfeld1.Text+" nächster Eintrag: "+Eingabe;
				};
				Invoke(Nachtragen);
			}

Das hat ohne weiteres funktioniert und ich konnte aus beliebigen Modulen Text in das Label schreiben.
Da ich aber gerne das Ausgabefenster-Modul nicht als Konsole mit eingebauter Forms-Klasse sondern gleich DIREKT als Forms-Anwendung schreiben möchte, muss der Invoke von den Modulen scheinbar etwas anders aussehen. Also wo ist genau der Unterschied zwischen dem Invoke einer Konsolenanwendung und dem Invoke einer Forms Anwendung?

Also das Hauptprogramm soll das Ausgabefenster aktivieren und die Module sollen es mit Informationen füttern. Ich habe es doch praktisch schon fertig aber irgendwo etwas übersehen. Mein Verdacht ist ja, dass der Aufruf des Ausgabefensters die Anwendung startet, aber die Module irgendwie versuchen, nochmal eine eigene Instanz anzusprechen. Deswegen ja das Problem mit dem Fenster-Handle.

Ich bin kein studierter Informatiker, ich habe mir die ganzen Grundlagen in den letzten 1,5 Jahren angeeignet und diese Aufgabe geht langsam echt an die Grenzen dessen was ich beherrsche. Wenn ich das ganze so erstmal zum laufen bringe kann ich meinen Kollegen wenigstens ein funktionierendes Programm liefern und während die damit arbeiten, habe ich vielleicht die Gelegenheit das ganze nochmal mit neuer Architektur zu programmieren.
Es kann doch echt keine Zauberei sein.

463 Beiträge seit 2009
vor 4 Jahren

Ist jetzt zwar ein wenig Offtopic - aber ich beschreibe dir einmal wie ich es machen würde:

Ich würde eine selbstständige Instanz (z.B. als Service) der Ausgabe erzeugen und in dieser auf einen Input aus einer Quelle (ich würde hier eine Datenbank verwenden) warten. Die anderen Module schreiben ihre Ausgaben nur noch in diese Quelle und gut ist.

Vorteil: Kein Modu muss wissen, wie die Ausgabeform aufgebaut ist, du kannst diese sogar beliebig verändern.. Und diese ganzen Reflection Teil kannst du damit ganz einfach weglassen...

R
Raketenmaulwurf Themenstarter:in
5 Beiträge seit 2019
vor 4 Jahren

Darf ich denn mal die Frage loswerden, warum Reflection hier offensichtlich so verteufelt wird? Bisher habe ich als einziges Argument dagegen die Performance gefunden, die aber in meinem Fall nicht wirklich von Belang ist. Mein Programm soll später teils stundenlange Arbeitsabläufe automatisieren und den Zeitaufwand von Stunden auf Minuten oder Sekunden reduzieren. Ob das Modul da wegen der dynamischen Zugriffe auf die Assemblies eine Sekunde länger braucht ist da total zweitrangig. Auch das geplante Ausgabefenster muss die Meldungen nicht auf die Nanosekunde auf den Bildschirm bringen.

Was ich an Reflection bisher sehr zu schätzen weiß ist die Möglichkeit, gezielt einzelne Methoden einer Assembly aufrufen zu können, ohne den restlichen Programmcode der Assembly kennen zu müssen. Ich picke mir also immer nur das heraus was ich brauche, ohne das mich der Rest irgendwie behindert.

Ich hatte mich schonmal in das Interface System reingelesen und ja es kann möglicherweise das selbe erreichen, aber ich habe deutlich mehr Aufwand bei der Programmierung, weil ich die Interfaces erst schreiben muss und daraus keinen (aus meiner Sicht erkennbaren) Mehrwert bekomme. Außerdem bin ich skeptisch, ob ein Interface hinterher so einfach erweitert werden kann. Wenn ich wegen einer neuen Funktion in einem Modul auch das komplette Interface System in ALLEN Modulen umschreiben muss dann habe ich da absolut nichts gekonnt. Und die Möglichkeit, mit möglichst wenig Aufwand Änderungen einzubringen hat oberste Priorität.

Mit Reflection kann ich eine neue Methode in einem Modul ganz einfach aus dem Hauptprogramm aufrufen mit ca. 1-2 Zeilen Code. Dabei ist es total egal ob die anderen Module diese Methode ebenfalls beinhalten.

Darf ich jetzt nochmal ganz höflich nachfragen, ob jemand eine Antwort auf meine ursprüngliche Frage hat?

Alternative Lösungen sind zwar für die Zukunft durchaus nützlich, wenn ich mal irgendwann eine neue Version meiner Software entwickle, aber im Moment hilft es mir leider gar nicht. Wie gesagt, mir fehlt einfach die Zeit alles nochmal neu aufzusetzen. Es muss jetzt erstmal so laufen und alles weitere zu seiner Zeit.

16.807 Beiträge seit 2008
vor 4 Jahren

Darf ich denn mal die Frage loswerden, warum Reflection hier offensichtlich so verteufelt wird?

Wir verteufeln nicht Reflection generell, sondern dass die Art und Weise, wie Du es einsetzen willst, Prinzipien von Software Architektur verletzt.
[Artikel] Drei-Schichten-Architektur

Was ich an Reflection bisher sehr zu schätzen weiß ist die Möglichkeit, gezielt einzelne Methoden einer Assembly aufrufen zu können, ohne den restlichen Programmcode der Assembly kennen zu müssen. Ich picke mir also immer nur das heraus was ich brauche, ohne das mich der Rest irgendwie behindert.

Und das ist eine verhältnismäßig gefährliche Angelegenheit.

R
Raketenmaulwurf Themenstarter:in
5 Beiträge seit 2019
vor 4 Jahren

Und was ist daran so gefährlich? Ich schreibe mein Hauptprogramm selbst und ich schreibe meine Module nach einer strengen Vorlage selbst, sodass jede Methode in einem Modul immer nur eine definierte Aufgabe erfüllt. Diese Vorlage ist sozusagen meine Form von Interface für die ich keinen extra Programmcode brauche. Es gibt immer eine Methode "Programm", die die jeweilige Aufgabe des Moduls enthält, jedes Modul soll jetzt eine definierte Methode enthalten, die einfach nur eine Meldung an das Ausgabefenster schickt, welches sich dann selbstständig um die Filterung kümmert.

Bisher läuft ein normaler Programmablauf so ab:

  • Das Hauptprogramm lädt alle Modul DLL-Dateien und prüft deren Version und ob sie mit der eingestellten Version des Hauptprogramms kompatibel sind. (Was wegen der Kompatibilität mit einer externen Software notwendig ist). Dazu frage ich die im Modul hinterlegte Version ab indem ich einfach eine Methode "Version" aufrufe, die mir die Version als string zurückgibt.

  • Geprüfte und kompatible Module werden in der Benutzeroberfläche angezeigt und können vom Bediener ausgewählt werden. Jedes Modul verfügt über ganz individuelle Optionen, die es zum Betrieb braucht. Die liegen prinzipiell in einer XML-Konfigurationsdatei für das Modul und können vor dem Start neu eingestellt werden.

  • Hat ein Modul alle Optionen bekommen kann es gestartet werden und führt danach seine Aufgabe aus und gibt bei Bedarf Meldungen aus.

  • Ist ein Modul durchgelaufen, gibt es eine Rückmeldung ans Hauptprogramm, was später wichtig wird, wenn die Module zu Makros zusammengefasst werden, die nacheinander ausgeführt werden sollen.

Und was den Artikel zur 3-Schichten-Architektur angeht, den habe ich in der Tat mehrfach gelesen und auch die Diskussion dazu inklusive der Einwände von ErfinderDesRades. Leider fällt es mir ohne das große Hintergrundwissen eines Informatikstudiums schwer, das alles so nachzuvollziehen. Ich programmiere zumeist so, dass jeder Programmteil nur die Informationen bekommt, die er zum funktionieren benötigt und auch in der Richtung in die die Informationen fließen sollen. Also erschließt sich mir beispielsweise nicht ganz, warum die Logikschicht nicht die Präsentationsschicht ansprechen darf, wenn doch aus der Logikschicht die Informationen kommen, die auf der Präsentationsschicht angezeigt werden sollen. Da müsste ich ja sozusagen von der Ausgabe aus ständig in allen Modulen nachfragen, ob es etwas zu melden gibt... Ob das aus Sicht der Performance wirklich ein Gewinn ist sehe ich noch nicht.

Ich bin nunmal Elektrotechniker. Da geht das Licht an wenn man den Schalter drückt und nicht wenn die Lampe nachfragt, ob sie angehen soll 😁

Ich bin mit meinem System bisher super gefahren und wie ich bisher mehrfach erwähnt habe kann ich im Moment aus Zeitgründen nicht alles umbauen. Ich habe das Beispiel aus dem ersten Post in einem anderem Forum gefunden, dort bestand es allerdings nur aus 2 "Teilen". Das Ausgabefenster wurde direkt aus seinem Programm heraus erschaffen und gestartet. Bei mir soll der Aufruf von extern kommen (Und das ist eine Anforderung die ich zu erfüllen habe) und die Informationen sollen wiederum aus anderen Quellen kommen. Und genau in diesem externen Aufruf scheint das Problem zu stecken was ich bisher nicht lösen konnte.

Das hier ist alles Quellcode Marke "Bastelkeller" es muss tun was es soll aber es muss dabei nicht schön aussehen. Niemand außerhalb meiner Abteilung soll jemals was davon zu sehen bekommen also sind die einzigen Leute die später meinen Code sehen meine Kollegen, die auch in der Lage sein wollen, eigene Module mit wenig Programmierkenntnissen zu schreiben. Und dafür finde ich Reflection mit seinem vergleichsweise einfachen Aufbau perfekt geeignet.

Ich weiß, dass ich damit das "Weltbild" der Informatik ignoriere und wenn ich später mal die Variante 2 entwerfe versuche ich alles einzuarbeiten.

16.807 Beiträge seit 2008
vor 4 Jahren

Ich bin nunmal Elektrotechniker. Da geht das Licht an wenn man den Schalter drückt und nicht wenn die Lampe nachfragt, ob sie angehen soll 😄

Der Vergleich hinkt.

Beispiel: Früher, bei der klassischen Elektroinstallation, hat man Leitungen im Haus einzeln verlegt.
Sprich man musste sich vorher Gedanken bzw. einen Plan machen: wo ist der Schalter, wo ist der Kanal, wo ist das Licht etc.
Änderungen waren nicht oder nur mit Aufwand (zB. Wand aufstemmen) möglich.

In moderne Häuser verbaut man mittlerweile mehr und mehr Bussysteme ein, wie zB KNX.
Hier ist es kein Problem mehr einfach Dinge nachzurüsten oder Dinge umzuprogrammieren, wenn sich der Raumaufbau oder das Verhalten ändern soll; man hat eine Registrierungsstelle in Form einer Schaltanlage, Sensoren und Aktoren.

Ähnlich ist es in Software:
Natürlich kannst Du alles hart codieren und Dir durch extreme Kopplung eine unflexible Struktur schaffen, die trotzdem funktioniert.
Erweiterungen sind quasi nicht oder nicht so einfach umsetzbar; geschweige von testbar.

Du kannst es aber auch so machen, wie man es eben heute machen würde:
Mit einem ordentlichen Pluginsystem.

Keiner hier kann Dich zwingen es ordentlich zu machen. Keiner hier sagt Dir "Bau alles um sonst ist es Scheisse". Software ist immer noch "nur" ein Problemlöser.
Du hast gefragt was das Problem mit Deiner Idee ist; wir haben geantwortet.
Das Forum kann nichts dafür, dass das Konzept am Anfang eben schon murks war und sich jetzt der rote Faden leider durchzieht.

Du kannst den Rat annehmen, oder eben nicht.
Du kannst die Hinweise annehmen, daraus lernen und in Zukunft anwenden - oder nicht 😉

Anekdote:
Hab neulich Code reviewed beim Kunden; er hat gemeint, dass er gerne Software Tests einführen würde.
Die Software ist Jahre gereift aber macht bei deren Kunden immer mehr Probleme, funktioniert nicht richtig, kostet Geld wenn Servicetechniker Vor-Ort Zeugs fixen müssen.

Wir haben vier Stunden einen Workshop gehalten und das Fazit des Kunden war

Wow, ich muss ja den ganzen Code umschreiben, dass ich ihn testen kann

Und ja, das muss er. Der Code war totale Grütze, von Vorne bis Hinten.
Völlig unstrukturiert, ohne Plan vorgegangen und jegliche Conventions und Pattern missachtet.
Klar, dass sich der Code eben nicht testen lies.

T
2.219 Beiträge seit 2008
vor 4 Jahren

Ich stimme Abt hier zu, dein Ansatz mit reflection istk omplett unsinnig.
Im Endeffekt kannst du dein Problem über das einladen der Assembly und saubere Interfaces lösen ohne Reflection nutzen zu müssen.

Du kannst durch deine Reflection auch nicht verhindern, dass dir jemand Datenschrott unterjubelt wenn er deine Arbeitsweise nachvollziehen kann.
Durch das laden der Assembly und arbeiten gegen ein Interfaces kannst du solchen Maßnahmen sauber einen Riegel vorschieben und auch saubers Fehlerhandling umsetzen.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.