Connect with us

Entwicklung & Code

software-architektur.tv: John Romeros Prinzipien – mit Tom Asel


John Romero ist bekannt als eine der treibenden Kräfte hinter id Software. Mit einer Gruppe von unter zehn Leuten hat id die Spiele-Welt mit „Doom“ und „Quake“ revolutioniert, Shareware als Vertriebsmodell etabliert, Spiele-Engines zu einem eigenständigen Produkt gemacht und viele seiner Innovationen später als Open Source veröffentlicht. In seinen aktuellen Vorträgen hat er einige fundamentale Prinzipien für die Softwareentwicklung diskutiert.

Weiterlesen nach der Anzeige

In dieser Episode sprechen Tom Asel und Eberhard Wolff darüber, was Developer daraus für die Entwicklung von Informationssystemen im Allgemeinen lernen können.

Die Ausstrahlung findet live am Freitag, dem 15. Mai 2026, um 13 Uhr statt. Die Folge steht im Anschluss als Aufzeichnung bereit. Während des Livestreams können Interessierte Fragen via Twitch-Chat, YouTube-Chat oder anonym über das Formular auf der Videocast-Seite einbringen.

software-architektur.tv ist ein Videocast von Eberhard Wolff, iX-Blogger und bekannter Softwarearchitekt, der als Head of Architecture bei SWAGLab arbeitet. Zum Team gehören außerdem Lisa Maria Schäfer (Socreatory) und Ralf D. Müller (DB Systel). Seit Juni 2020 sind über 250 Folgen entstanden, die unterschiedliche Bereiche der Softwarearchitektur beleuchten – mal mit Gästen, mal Wolff, Schäfer oder Müller solo. Seit mittlerweile mehr als zwei Jahren berichtet heise developer über die Episoden.


(who)



Source link

Entwicklung & Code

Neu in .NET 10.0 [23]: Neue LINQ-Operatoren LeftJoin() und RightJoin()


Wie in den letzten .NET-Versionen auch, liefert Microsoft in .NET 10.0 wieder neue Operatoren für Language Integrated Query (LINQ), die bestehende Konstrukte vereinfachen. Dieses Mal sind es mit LeftJoin() und RightJoin() zwei elementare Operatoren, zum einen aus der Mengenlehre und zum anderen für relationale Datenbanken. Mehr dazu steht im zugehörigen Issue auf GitHub.

Weiterlesen nach der Anzeige


Der Dotnet-Doktor – Holger Schwichtenberg

Der Dotnet-Doktor – Holger Schwichtenberg

Dr. Holger Schwichtenberg ist technischer Leiter des Expertennetzwerks www.IT-Visions.de, das mit 53 renommierten Experten zahlreiche mittlere und große Unternehmen durch Beratungen und Schulungen sowie bei der Softwareentwicklung unterstützt. Durch seine Auftritte auf zahlreichen nationalen und internationalen Fachkonferenzen sowie mehr als 90 Fachbücher und mehr als 1500 Fachartikel gehört Holger Schwichtenberg zu den bekanntesten Experten für .NET und Webtechniken in Deutschland.

Tatsächlich waren diese Operationen bisher in LINQ bereits möglich, allerdings nur umständlich über eine Gruppierung mit GroupJoin() und SelectMany() sowie DefaultIfEmpty(). Die neuen Methoden LeftJoin() und RightJoin() vereinfachen den Einsatz, wie folgender Code am Beispiel eines Join zwischen den Klassen Company und Website zeigt:


public void LeftRightJoin()
{
 CUI.Demo();
 
 Company[] companies =
 [
   new Company{ ID = 1, Name = "www.IT-Visions.de" },
   new Company{ ID = 2, Name = "Software & Support" },
   new Company{ ID = 3, Name = "Heise Gruppe GmbH & Co. KG" },
   new Company{ ID = 4, Name = "Startup i.Gr." } // hat noch keine Website
 ];
 
 Website[] websites =
 [
   new Website{ CompanyID = 1, URL = "www.IT-Visions.de" },
   new Website{ CompanyID = 1, URL = "www.dotnet10.de" },
   new Website{ CompanyID = 2, URL = "www.entwickler.de" },
   new Website{ URL = "www.Microsoft.com" }, // Diese kleine ;-) Firma ist noch nicht angelegt...
   new Website{ CompanyID = 3, URL = "www.heise.de" },
 ];
 
 // ### LeftJOIN ALT ############################################################################
 
 CUI.H2("--- Alle Firmen mit ggf. vorhandenen Websites via LeftJoin ALT (GroupJoin+SelectMany) seit .NET Framework 3.5 ---");
 
 var AllCompaniesWithWebsitesSetOld = companies
     .GroupJoin(websites,
                c => c.ID,
                w => w.CompanyID,
                (c, websites) => new { Company = c, Websites = websites })
     .SelectMany(
         x => x.Websites.DefaultIfEmpty(),  // Falls keine Website existiert, wird `null` verwendet
         (c, w) => new WebsiteWithCompany
         {
          Name = c.Company.Name,
          URL = w.URL,   // Falls `w` null ist, bleibt URL null
          City = c.Company.City
         });
 
 foreach (var item in AllCompaniesWithWebsitesSetOld)
 {
  Console.WriteLine((item.Name != null ? item.Name + " " + item.City : "- keine Firma -").Trim() + " -> " + (item.URL ?? "- keine URL -"));
 }
 
 // ### LeftJOIN NEU ############################################################################
 
 CUI.H2("--- Alle Firmen mit ggf. vorhandenen Websites via LeftJoin NEU ab .NET 10.0 ---");
 var AllCompaniesWithWebsitesSet = companies.LeftJoin(websites,
  e => e.ID,
  e => e.CompanyID,
  (c, w) => new WebsiteWithCompany { Name = c.Name, City = c.City, URL = w.URL }
  );
 
 foreach (var item in AllCompaniesWithWebsitesSet)
 {
  Console.WriteLine((item.Name != null ? item.Name + " " + item.City : "- keine Firma -").Trim() + " -> " + (item.URL ?? "- keine URL -"));
 }
 
 // ### RightJoin ALT ############################################################################
 
 CUI.H2("--- Alle Websites mit ggf. vorhandenen Firmen via RightJoin ALT (GroupJoin+SelectMany) seit .NET Framework 3.5  ---");
 var WebsiteWithCompanySetOLD = websites
     .GroupJoin(companies,
                w => w.CompanyID,
                c => c.ID,
                (w, companies) => new { Website = w, Companies = companies })
     .SelectMany(
         x => x.Companies.DefaultIfEmpty(),  // Falls kein Unternehmen existiert -> null
         (w, c) => new WebsiteWithCompany
         {
          Name = c.Name,  // Falls `c` null ist, bleibt `Name` null
          City = c.City,   // Falls `c` null ist, bleibt `City` null
          URL = w.Website.URL
         });
 
 foreach (var item in WebsiteWithCompanySetOLD)
 {
  Console.WriteLine((item.Name != null ? item.Name + " " + item.City : "- keine Firma -").Trim() + " -> " + (item.URL ?? "- keine URL -"));
 }
 
 // ### RightJoin NEU ############################################################################
 
 CUI.H2("--- Alle Websites mit ggf. vorhandenen Firmen via RightJoin NEU ab .NET 10.0 ---");
 var WebsiteWithCompanySet = companies.RightJoin(websites,
     e => e.ID,
     e => e.CompanyID,
     (c, w) => new WebsiteWithCompany { Name = c.Name, City = c.City, URL = w.URL }
  );
 
 foreach (var item in WebsiteWithCompanySet)
 {
  Console.WriteLine((item.Name != null ? item.Name + " " + item.City : "- keine Firma -").Trim() + " -> " + (item.URL ?? "- keine URL -"));
 }
 
 // ### Zum Vergleich: Inner Join, den es seit .NET Framework 3.5 gibt
 CUI.H2("--- Alle Firmen, die Websites haben, via InnerJoin seit .NET Framework 3.5 ---");
 var CompaniesWithWebsitesSet = companies.Join(websites,
                                        c => c.ID,
                                        w => w.CompanyID,
                                        (c, w) => new WebsiteWithCompany
                                        {
                                         Name = c.Name,
                                         URL = w.URL,
                                         City = c.City
                                        });
 foreach (var item in CompaniesWithWebsitesSet)
 {
  Console.WriteLine((item.Name != null ? item.Name + " " + item.City : "- keine Firma -").Trim() + " -> " + (item.URL ?? "- keine URL -"));
 }
}



Screenshot

Screenshot

Der Code erzeugt diese Ausgabe (Abb. 1).

Die neuen Operatoren sind laut Microsoft performanter als die bisherige Operatorkombination.


Vergleichstabelle

Vergleichstabelle

Performance von LeftJoin() im Vergleich zu der bisherigen Operatorkombination (Abb. 2)

(Bild: Microsoft)


(rme)



Source link

Weiterlesen

Entwicklung & Code

Von undefiniert zu definiert: Die Verwendung von std::launder in C++


Im heutigen Beitrag knüpfe ich an die übergreifenden Themen der letzten beiden Monate an. Heute geht es darum, wann und wo du std::launder aus C++17 einsetzen musst und worin der Unterschied zu reinterpret_cast oder std::start_lifetime_as besteht.

Weiterlesen nach der Anzeige


Portrait von Andreas Fertig

Portrait von Andreas Fertig

Andreas Fertig ist erfahrener C++-Trainer und Berater, der weltweit Präsenz- sowie Remote-Kurse anbietet. Er engagiert sich im C++-Standardisierungskomitee und spricht regelmäßig auf internationalen Konferenzen. Mit C++ Insights ( hat er ein international anerkanntes Tool entwickelt, das C++-Programmierenden hilft, C++ noch besser zu verstehen.

Die Bereiche, in denen man das heute Gelernte anwenden kann, sind vielfältig. Im Embedded-Bereich wird std::launder in der Regel verwendet, aber auch beim Schreiben von Bibliothekscode kommt „Laundering“ (zu Deutsch waschen und bügeln, gerne im Zusammenhang mit „Money“ als Geldwäsche) vor.

Ich verwende das Beispiel aus dem Paper P0532R0:


struct X {
  const int n;  // #A
  double    d;
};

X* p = new X{7, 8.8};  // #B

new(p) X{42, 9.9};  // #C

int  i = p->n;  // #D
auto d = p->d;  // #E


Hier stehen mehrere Teile, die zusammenpassen müssen. Beachte, dass das struct X das Datenfeld n als const deklariert.

Als Nächstes wird mithilfe von new in #B ein Objekt erstellt und der resultierende Zeiger in p gespeichert. So weit, so gut.

Weiterlesen nach der Anzeige

Der interessante Teil beginnt als Nächstes in #C mit dem Platzierungs-new. Wer das noch nie gemacht hat, muss den eigenen C++-Code vielleicht nicht waschen und glatt bügeln.

Das Platzierungs-new selbst ist auch in Ordnung. Das Problem tritt später in #D und #E auf. Hier wird auf die Werte des Zeigers p zugegriffen. Aber der Compiler darf davon ausgehen, dass nach #B der Inhalt von p unverändert ist, da p selbst nie dazu verwendet wurde, den Inhalt des Objekts zu ändern. Noch schlimmer: Eines der Datenelemente von X ist const. Das ist eine Freikarte für den Optimierer, zu Recht anzunehmen, dass sich der Wert von n nach der Konstruktion nie ändert.

Aber genau das habe ich in #C getan. Ich ändere einen const-Wert! Die Werte von i und d sind unbekannt. Das ist undefiniertes Verhalten.

Dieses lässt sich leicht vermeiden, sogar ohne std::launder, indem man den Zeiger aktualisiert und dem Compiler mitteilt, dass sich die Werte hinter p geändert haben:


X* p = new X{7, 8.8};  // #B

p = new(p) X{42, 9.9};  // #C

int  i = p->n;  // #D
auto d = p->d;  // #E


Die Frage ist also: Warum macht man das nicht einfach und vergisst std::launder? Nun, wann immer möglich, vergiss std::launder.

Leider gibt es Fälle, in denen das Aktualisieren des Zeigers wertvolle Ressourcen opfern würde.

Angenommen, du implementierst einen benutzerdefinierten Allokator:


template
class Buffer {
  alignas(ALIGNMENT) std::byte mBuffer[SIZE];

public:
  template
  T* Construct(Ts... vals)
  {
    new(mBuffer) T{std::forward(vals)...};
    return reinterpret_cast(mBuffer);
  }

  template
  [[nodiscard]] T* Get()
  {
    return reinterpret_cast(mBuffer);
  }
};


Du siehst hier zwei Funktionen, die der Allokator bereitstellt: Construct und Get. Das Konzept von Buffer besteht darin, dass die gespeicherten Daten typunabhängig sind. Eine mögliche vereinfachte Verwendung könnte folgendermaßen aussehen:


struct Point {  // #A
  int x;
  int y;
};

struct Point3D {
  int x;
  int y;
  int z;
};

std::array, 2> storage{};  // #B

// #C
storage.at(0).Construct(2, 3);
storage.at(1).Construct(4, 5, 6);

// #D
storage.at(0).Get()->x = 7;


Die zwei Datentypen Point und Point3D in #A stehen als Beispiel für beliebige Typen. storage steht für den Stack-Speicher, der beliebige Daten speichern kann. Solange die Nutzer wissen, welcher Datentyp hinter einem Index steckt (und dieser Typ nicht zu groß ist), kann alles gespeichert werden.

Am ersten Element des Arrays #C konstruiere ich ein Point-Objekt, während ich am zweiten Element ein Point3D erstelle. Erst später im Programm #D werden die Objekte tatsächlich verwendet. Dennoch hat niemand aus gutem Grund einen Zeiger auf das frisch konstruierte Objekt gespeichert. Ein solcher Zeiger würde Speicherkapazität erfordern, und du weißt bereits, wohin dieser Zeiger zeigt.

Aus der Perspektive der abstrakten Maschine habe ich Löcher in das Typsystem gestochen. Der Compiler kann zu Recht davon ausgehen, dass sich die Daten hinter Get nicht ändern, es sei denn, er sieht etwa einen direkten Schreibzugriff #D. Ein solcher Zugriff bleibt vom Compiler unbemerkt (oder könnte es bleiben, da es sich um undefiniertes Verhalten handelt) im folgenden Code in #E, wenn ich an einer bestehenden Stelle ein neues Point-Objekt erstelle:


std::array, 2> storage{};  // #B

// #C
storage.at(0).Construct(2, 3);

// #D
storage.at(0).Get()->x = 7;

// #E
storage.at(0).Construct(8, 9);


Offensichtlich lässt sich das Problem hier am einfachsten vermeiden, indem man den von jedem Construct-Aufruf zurückgegebenen Zeiger speichert. Wenn das, wie hier, nicht machbar ist, besteht die richtige Vorgehensweise darin, den Zeiger mit std::launder zu „waschen“. Dieses spezielle Hilfsmittel fungiert als Devirtualisierungsbarriere, die Compiler-Optimierungen und -Annahmen verhindert.

So lässt sich die Buffer-Implementierung auf sichere Weise aktualisieren. Zunächst zu Construct: Meine ursprüngliche Implementierung sah so aus:


template
T* Construct(Ts... vals)
{
  new(mBuffer) T{std::forward(vals)...};
  return reinterpret_cast(mBuffer);
}


Du kannst diesen Code auch ohne std::launder sicher machen, indem du den von new zurückgegebenen Zeiger zurückgibst:


template
T* Construct(Ts... vals)
{
  return new(mBuffer) T{std::forward(vals)...};
}


Die zweite Funktion, die du korrigieren musst, ist Get, dieses Mal durch Hinzufügen von std::launder:


template
[[nodiscard]] T* Get()
{
  return std::launder(reinterpret_cast(mBuffer));
}


Der Code geht nun davon aus, dass du Construct aufrufst, bevor du Get aufrufst. Dieser mBuffer enthält ein gültiges Objekt, das mit dem übereinstimmt, das mit Construct erstellt wurde.

Mit std::launder kannst du einen Zeiger aktualisieren, der auf ein bereits vorhandenes Objekt an dieser Speicheradresse verweist. Die Lebensdauer des Objekts an dieser Stelle hat also bereits begonnen.


(rme)



Source link

Weiterlesen

Entwicklung & Code

KI-Portierung: Claude schreibt Bun-Codebasis in Rust neu


Bun ist einst angetreten, um Node.js als JavaScript-Server, NPM als Paketmanager und Bundler wie esbuild mit einer Software zu ersetzen. Ende 2025 übernahm die KI-Firma Anthropic das Open-Source-Projekt und das Bun-Entwicklerteam. Die Begründung: Anthropic nutzt Bun bereits für Claude Code und das Claude Agent SDK.

Weiterlesen nach der Anzeige

Jetzt wird klar, dass die Übernahme des Projekts durch die KI-Firma Anthropic nicht folgenlos bleibt: Ende April erschien ein Branch, in dem das Sprachmodell Claude auf Anweisung die gesamte Codebasis von Zig auf Rust umzieht. Noch am 5. Mai ordnete Jarred Sumner, Gründer von Bun, diese Entwicklung ein und versuchte damit, eine Diskussion zu beruhigen: „I work on Bun and this is my branch. This whole thread is an overreaction. 302 comments about code that does not work. We haven’t committed to rewriting. There’s a very high chance all this code gets thrown out completely.“

Doch es kam anders und die Ansätze wurden nicht verworfen: Am 14 Mai wurden die Änderungen vielmehr in den Main-Branch übernommen – 2188 Dateien geändert, eine Million Zeilen neu geschrieben, 4000 Zeilen gelöscht. Bun ist damit komplett in Rust geschrieben.

Dieses Vorgehen stößt nicht nur auf Begeisterung. Während Jarred Sumner auf GitHub ankündigt, Details im Blog veröffentlichen zu wollen, beginnt die Diskussion unter dem Beitrag. Kritisiert wird unter anderem, dass einige der alten Tests verändert wurden, damit die Rust-Version sie erfolgreich durchläuft. Auf Jarreds Aussage „We now have compiler-assisted tools for catching & preventing memory bugs“ reagieren Kommentatoren, dass das nur zutreffe, wenn man im Code nicht inflationär das Schlüsselwort unsafe verwende, wie Claude es getan habe.

Weiterlesen nach der Anzeige

Für das Bun-Team beginnt der größte Teil der Arbeit jetzt: In den GitHub-Issues sammeln sich bereits die ersten Probleme, die mit der Zig-Version nicht auftraten. Noch ist die Rust-Version nicht mit Versionsnummer veröffentlicht, doch das scheint nur eine Frage der Zeit zu sein. Wer Bun nutzt und zunächst abwarten möchte, sollte seine Version auf 1.3.14 festnageln. Das könnte die letzte Zig-Version bleiben.

Ein kompletter Umzug eines Projekts dieser Größe, komplett von einem LLM erledigt – das ist ein Novum in der Softwareentwicklung. Wie viele Token Bun dafür aufgewendet hat, verrät Jarred bisher nicht. Erst mit dieser Information könnte man errechnen, was ein solcher Umzug kosten würde, wenn man nicht gerade von Anthropic übernommen wurde.


(jam)



Source link

Weiterlesen

Beliebt