Skip to main content

Autor: Johannes Schneider

Der Mythos vom „Fullstack-Entwickler“. Oder: Die Suche nach dem Einhorn!

Willkommen zu einem weiteren Abenteuer in der Welt der Softwareentwicklung! Heute tauchen wir in die mystische Sphäre des sagenumwobenen „Fullstack-Entwicklers“ ein. Ja, du hast richtig gehört – jener geheimnisvolle Zauberer, der gleichzeitig die Macht des Frontends und des Backends beherrscht. Aber gibt es ihn wirklich, oder ist er nur ein Mythos? Eine moderne Version des Einhorns, das wir alle jagen, aber niemals wirklich finden?

Was ist ein „Fullstack-Entwickler“?

Bevor wir uns auf die Suche nach dem Einhorn begeben, klären wir doch erst einmal, was mit „Fullstack-Entwickler“ überhaupt gemeint ist. Theoretisch handelt es sich um jemanden, der sowohl das Frontend – also das, was der Benutzer sieht und womit er interagiert – als auch das Backend – das, was im Hintergrund abläuft und die Daten verwaltet – beherrscht. Diese Entwickler kennen sich sowohl mit HTML, CSS und JavaScript aus als auch mit Server-Architekturen, Datenbanken und beherrscht mehrere Programmiersprachen Java und TypeScript.

Die Realität: Ein universelles Genie oder doch nur Wunschdenken?

Nun zur großen Frage: Gibt es den Fullstack-Entwickler wirklich? In der Theorie klingt das alles wunderbar. Ein Entwickler, der alles kann – das ist der Traum eines jeden Unternehmens! Wer würde nicht gerne jemanden einstellen, der sowohl die hübsche Benutzeroberfläche zaubern als auch die komplexe Serverlogik im Hintergrund orchestrieren kann?

Aber Moment mal! In der Realität stellt sich schnell heraus, dass die Anforderungen an einen Fullstack-Entwickler oftmals in den Bereich des Unmöglichen fallen. Die Technologien in beiden Bereichen entwickeln sich so rasant weiter, dass es schwer ist, in allen Feldern stets auf dem neuesten Stand zu bleiben. Selbst die besten Entwickler müssen sich spezialisieren, um in einem Bereich wirklich herausragend zu sein.

Der Mythos vom „Alleskönner“

Der Gedanke des „Fullstack-Entwicklers“ ist daher oft eher ein Marketing-Gag oder Wunschdenken als Realität. Klar, es gibt Entwickler, die sowohl ein solides Grundverständnis von Frontend- als auch Backend-Technologien haben. Doch ein tiefes, umfassendes Wissen in beiden Bereichen? Das ist schon schwieriger.

Was Unternehmen eigentlich suchen, sind Entwickler mit einer „Fullstack-Mentalität“: Menschen, die bereit sind, über den Tellerrand ihres Fachgebiets hinauszuschauen, die sich in neue Technologien einarbeiten und flexibel zwischen verschiedenen Aufgaben wechseln können. Aber das ist ein großer Unterschied zu dem, was oft erwartet wird.

Die Suche nach dem Einhorn

Hier sind wir also, auf der Suche nach dem Einhorn, dem „Fullstack-Entwickler“. Wir durchforsten Jobbörsen, schicken LinkedIn-Nachrichten, interviewen Kandidaten… und stoßen immer wieder auf dieselbe Erkenntnis: Ein echter „Fullstack-Entwickler“ ist so selten wie ein Einhorn. Die meisten Entwickler haben ihre Stärken und Leidenschaften entweder im Frontend oder im Backend, und das ist auch gut so!

Die Moral der Geschichte? Vielleicht sollten wir aufhören, nach Einhörnern zu suchen, und stattdessen lernen, die einzigartigen Fähigkeiten jedes Entwicklers zu schätzen. Teams sind dann am erfolgreichsten, wenn sie aus Spezialisten bestehen, die zusammenarbeiten und sich gegenseitig ergänzen.

Fazit: Ein Hoch auf die (teamfähigen!) Spezialisten

Statt den Mythos des Fullstack-Entwicklers weiter zu glorifizieren, sollten wir uns vielleicht auf das konzentrieren, was wirklich wichtig ist: Zusammenarbeit, kontinuierliches Lernen und die Bereitschaft, über den Tellerrand hinauszuschauen. Jeder Entwickler bringt seine eigenen Fähigkeiten und Erfahrungen mit ein – und genau das macht ein starkes Team aus.

Also, das nächste Mal, wenn du auf der Suche nach einem „Fullstack-Entwickler“ bist, frag dich lieber: Will ich wirklich ein Einhorn? Oder wäre ein Team aus talentierten, spezialisierten Entwicklern, die sich gegenseitig ergänzen, nicht viel wertvoller?

Das Einhorn mag zwar ein schöner Mythos sein, aber wahre Magie entsteht durch Zusammenarbeit und gemeinsame Leidenschaft. Und das ist kein Mythos.

Kotlin macht Spaß: Sealed Interfaces + when

Kotlin hat sich aus verschiedenen Programmiersprachen das Beste zusammengeklaut.
So erlaubt die Kombination von Sealed Interfaces und when sehr schönen und klaren Code. Der gleichzeitig auch noch sehr gut vom Compiler auf Korrektheit überprüft wird!

// Stub function to simulate fetching a response
private fun getResponse(): Response = TODO()

fun main() {
  // Call a method with a relaxed return type. 
  // This ensures the compiler treats 'response' as 'Response' rather than a specific subtype, avoiding compile-time warnings.
  val response: Response = getResponse()

  // Using 'when' with all concrete types. Skipping one type results in a compile error due to exhaustiveness checking.
  when (response) {
    is Response.Success.Success1 -> println("S1")
    is Response.Success.Success2 -> println("S2")
    is Response.Failure.Failure1 -> println("F1")
    is Response.Failure.Failure2 -> println("F2")
  }

  // Handling multiple types in one code block using commas.
  when (response) {
    is Response.Success.Success1,
    is Response.Success.Success2,
    is Response.Failure.Failure1,
    -> println("combined S1, S2, F1")  // Note the trailing comma for readability

    is Response.Failure.Failure2 -> println("F2")
  }

  // Handling all failure cases in one block by using the common interface 'Failure'
  when (response) {
    is Response.Success.Success1 -> println("S1")
    is Response.Success.Success2 -> println("S2")
    is Response.Failure -> println("Failure case") // This will match Failure1 and Failure2 both
  }

  // Handling Success and Failure cases together by their common interfaces
  when (response) {
    is Response.Success -> println("Success case")  // This will match Success1 and Success2
    is Response.Failure -> println("Failure case")  // This will match Failure1 and Failure2
  }
}

// Define a sealed interface 'Response' which can be either a Success or a Failure
sealed interface Response {
  
  // Define a nested sealed interface 'Success' under 'Response'
  sealed interface Success : Response {
    // Define concrete data classes for Success1 and Success2 which implement the Success interface
    data class Success1(val foo: String) : Success
    data class Success2(val bar: String) : Success
  }

  // Define a nested sealed interface 'Failure' under 'Response'
  sealed interface Failure : Response {
    // Define concrete data classes for Failure1 and Failure2 which implement the Failure interface
    data class Failure1(val foo: String) : Failure
    data class Failure2(val foo: String) : Failure
  }
}

Vorteile / Features

Sealed Interface-Hierarchie

Das Response-Interface ist sealed, was bedeutet, dass alle möglichen Unterklassen zur Compile-Time bekannt sind. Dadurch ist der when-Ausdruck exhaustiv – wir können nichts vergessen!

Unterschiedliche „Genaugigkeiten“ beim Machting

Durch die Typ-Hierarchie innerhalb der Sealed Interface können wir jetzt unterschiedliche Genauigkeiten matchen. Wir können z.B. alles Failure oder Success-Cases gemeinsam abhandeln.
Der Compiler übernimmt die Checks und fügt „Smart Casts“ hinzu.

Diese Beispiele zeigen, wie mächtig und flexibel sealed Interfaces und der when-Ausdruck in Kotlin sind, um komplexe Typ-Hierarchien zu handhaben und die Kompilierzeit-Sicherheit zu gewährleisten.

10 Gründe warum ein Scrum-Team aus Freelancern scheitern wird!

Ein Scrum-Team aus guten Freelancern zusammen stellen – was könnte da schon schief laufen?

Schließlich muss ein Team aus sehr guten Personen doch auch sehr gut performen, oder???

Sage mir, woran du mich misst, und ich sage dir, wie ich mich verhalten werde.

In der Regeln nach drei Monaten, spätestens nach sechs Monaten steht bei den meisten Freelancern die Vertragsverlängerung an.
Entsprechend passen Freelancer ihr Verhalten an diesen Zyklus an.

Welche Freelancer sind erfolgreich?

Wer wird als Freelancer beauftragt? Meiner Erfahrung nach sind das nicht unbedingt die besten Entwickler.
Oft genug sind es die guten Verkäufer (Selbstdarsteller?) und Menschen mit ausgesprochen gutem Netzwerk.

1. Nach der Vertragsverlängerung ist vor der Vertragsverlängerung

Jeder Freelancer lebt in einem 3/6-Monats-Zyklus von Verlängerung zu Verlängerung. Der Kunde muss immer zufrieden sein. Jeder Freelancer muss sich laufend beweisen, um die Verlängerung zu erhalten.

Kurzfristiges „beweisen“ ist daher Prio 1 für jeden Freelancer. Langfristige Entwicklungen und der „Dienst am Team“ müssen entsprechend hinten anstehen.

2. Niemand hat Zeit und Lust auf unsichtbare/undankbare Tätigkeiten

In jedem guten Scrum-Team gibt es die eher unscheinbaren Personen, die durch Fleiß und sorgfältige Arbeit das Team produktiv halten. Es gibt jede Menge unsichtbarer aber essenzieller Tätigkeiten, die unbedingt erledigt werden müssen.

Wer kümmert sich um die Dokumentation und testet den Code?
Wer führt sorgfältige und hilfreiche Code Reviews durch?
Wer kümmert sich um das Build Management und löst die Probleme?
Wer unterstützt die anderen Team-Mitglieder bei deren Tätigkeit und Problemen?
Wer hilft dabei die Kommunikation im Team zu optimieren?

Typischerweise wird keine dieser Tätigkeiten nach Außen hin deutlich sichtbar.

3. Alpha-Tiere unter sich

Der typische Freelancer tritt selbstbewusst auf und ist es gewohnt Dinge zu entscheiden und voran zu treiben. Warum sonst sollte das Risiko und die Herausforderungen der Selbständigkeit auf sich genommen werden?

Es ist leicht auszumalen, wie gut ein Team harmoniert, wenn es aus lauter (zumindest gefühlten) Alpha-Tieren besteht.

4. Freelancer haben wenig langfristige Erfahrungen

Die meisten Tätigkeiten von Freelancern sind kurzfristiger Natur. Entsprechend kennen viele Freelancer die langfristigen Folgen ihrer Entscheidungen nur aus der Ferne.

Erfahrung entsteht nicht durch das Wiederholen der selben Fehler und das Weiterziehen zum nächsten Projekt. Erfahrung entsteht durch das „Ausbaden“ von und Lösen von Fehlern, die sich erst im Nachhinein als Fehler herausstellen.

5. Freelancer verstehen „alte Projekte“ nicht

Durch das kurzfristige Engagement kennen Freelancer die Nöte und Schwierigkeiten von alten Projekten mit den ganzen Implikationen nicht. Freelancer sind oftmals gut darin neue Dinge zu entwickeln.
Wenn es aber um das Thema Migration alter Daten geht, hört die Erfahrung oftmals aus.

Das typische 80%/20%-Phänomen tritt auf. Die ersten 80% der Umsetzung verlaufen einwandfrei. Und bevor es dann ans Eingemachte geht, ist der Freelancer bereits beim nächsten Kunden aktiv…

6. Kontinuität fehlt

Ein Scrum-Team lebt zu sehr hohem Maße davon, dass das Team an sich funktioniert. Es geht dabei nicht nur um Know-How und Abläufe. Sondern auch um die Chemie und das Vertrauen im Team selbst.
Jeder Änderung am Team erfordert eine „Rekalibrierung“ des Teams.

Mit jeder Personaländerung im Team sinkt die Produktivität im Team für eine gewisse Zeit.

Je mehr Freelancer in einem Team sind, desto größer ist die Fluktuation im Team.

7. Keine gemeinsame Kultur

Jedes Unternehmen entwickelt – bewusst oder unbewusst – eine eigene Kultur. Die Menschen im Unternehmen teilen diese Kultur und passen sich dieser im Laufe der Zeit immer mehr an.

Jeder Freelancer bringt seine eigenen Vorstellungen und Werte bezüglich der Projekt-Kultur mit. Entsprechend dauert es länger bis sich das Team „findet“.

8. Unehrliche Retrospektive („alles super!“)

Um in der Retro (halbwegs) ehrlich miteinander sein zu können, ist ein sicheres Umfeld notwendig. Wenn die nächste Vertragsverhandlung direkt vor der Tür steht, fällt es schwer ehrlich zu sein.
Solche Retros sind daran zu erkennen, dass nur Positives geäußert wird. Höchstens über die Umstände und externe Dinge außerhalb des Einflusses des Teams wird negativ berichtet.

9. Wenig Identifikation mit dem Team bzw. dem Ziel des Teams

Jeder Freelancer ist per Definition Einzelkämpfer. Durch die Kurzfristigkeit der Tätigkeit ist eine Identifikation mit dem Team bzw. dem Ziel des Teams quasi unmöglich.
Ohne diese Identifikation mit dem gemeinsamen Ziel ist eine optimale Zusammenarbeit sehr schwer.

10. Wissenverlust

Die Fluktuation sorgt für beträchtlichen Wissensverlust. Dieser wird dadurch verstärkt, dass die Freelancer das langfristige Ziel des Projektes nicht teilen. Entsprechend steht der schnelle Erfolg und weniger die langfristige Qualität im Vordergrund.

Die Alternative?

Ein Team aus Freelancern wird nur in den seltensten Ausnahmefällen gut funktionieren. Im Prinzip bleiben nur zwei Alternativen:

  • Eigenes Team aufbauen
  • Komplettes Team engagieren

Komplettes Scrum-Team engagieren

Ein fertiges Team zu engagieren hat viele Vorteile:

  • Das Team ist bereits eingespielt und hat die unproduktive „Findungs-Phase“ bereits hinters ich
  • Das Team ist stabil und die Mitglieder haben ihre Rollen gefunden
  • Die Team-Mitglieder haben Sicherheit innerhalb des Teams und können entsprechend offen und ehrlich agieren
  • Das Team verfolgt gemeinsam ein Ziel und kann sich damit gemeinsam identifizieren
  • Das Team hat eine eigene Kultur entwickelt
  • Alle im Team haben ein persönliches Interesse am Erfolg des Gesamt-Projekts

6 einfache Regeln für Bootstrap-Dummies

anbei eine Kurz-Anleitung für das Bootstrap Grid-System. Wenn ihr euch daran haltet, sollte eigentlich nix mehr schief gehen.

[Regel 1] container oder container-fluid verwenden

Diese Klassen sorgen für die richtige Ausrichtung und Abstände zwischen den Elementen. container gibt eine feste Breite vor, während container-fluid die volle Breite des Viewports einnimmt.

[Regel 2] Rows zuerst

Jede .col muss immer in einer .row sein. Ohne .row wird die .col nicht korrekt dargestellt, da .row den notwendigen negativen Margin ausgleicht, der durch das Padding der .col erzeugt wird.

[Regel 3] 12 Cols nebeneinander

Das Grid-System von Bootstrap ist in 12 Spalten unterteilt. Diese ermöglichen das Nebeneinanderstellen von Elementen. Wenn du mehr als 12 Spalten innerhalb einer .row verwendest, erfolgt ein automatischer Umbruch.

[Regel 3.1] Bei mehr als 12 Cols: Automatischer Umbruch

Wird die Anzahl von 12 Spalten in einer Reihe überschritten, werden die zusätzlichen Spalten auf die nächste Zeile umgebrochen.

[Regel 4] Breakpoints: col-sm-* (576px)

Mit der Klasse col-sm-* werden Spalten für Bildschirme mit einer Breite unter 576px auf volle Breite gesetzt und untereinander dargestellt, um besser auf Mobilgeräten angezeigt zu werden.

Das ist ausreichend, wenn man nur zwei Versionen pflegen möchte.

[Regel 5] Verschachtelung möglich

Man kann .rows in .cols verschachteln, um komplexere und maßgeschneiderte Layouts zu erstellen. Denke daran, dass verschachtelte .rows in einer .col ebenfalls bis zu 12 Spalten haben können.

[Regel 6] Vertikal zentrieren mit d-flex align-items-center

Um Inhalte vertikal zu zentrieren, kannst du die Klasse d-flex zusammen mit align-items-center verwenden:

<div class="col-sm-6 d-flex align-items-center">

[Kotlin] Varags und nullable Types

Gerade eben hatte ich den Fall, dass ich einen nullable Type als Parameter an eine varags-Methode übergeben wollte.
Der varargs-Typ ist aber natürlich nicht nullable.

fun methodWithVarargs(vararg names: String) {[…]}

val nameOrNull: String? = null
methodWithVarargs(nameOrNull) //Does NOT compile!

Was also tun?

Die Lösung

Für Listen gibt es die schöne Methode listOfNotNull(). Leider noch nicht für Arrays.
Deshalb habe ich mir eine kleine Extension-Methode erstellt:

/**
 * Returns an array that contains this (if not null) or is empty (if this == null)
 */
inline fun <reified T> T?.arrayOfNotNull(): Array<T> {
  return if (this != null) arrayOf(this) else emptyArray()
}

Bitte beachten: „reified“ ist notwendig, da in der Methode ein Array vom konkreten Type erzeugt wird. (Bei Listen ist die Typinformation zur Laufzeit dagegen nicht enthalten).