Go heeft strenge regels voor de initialisatie van pakketten, variabelen en functies bij het starten van een programma. De belangrijkste mechanismen zijn het uitvoeren van init-functies en de initialisatie van globale variabelen. Een goed begrip van deze processen is belangrijk om fouten en onverwachte effecten te voorkomen.
Geschiedenis van de kwestie:
Go heeft vanaf het begin een strikte scheiding van opstartfases geïntroduceerd: declaratie, initialisatie en verdere uitvoering van de code. In talen zoals C/C++ worden vaak constructors van globale variabelen gebruikt, terwijl de volgorde van initialisatie in Go deterministisch is, maar er zijn nuances.
Probleem:
Het is gemakkelijk om in de val te trappen wanneer de initialisatie van globale variabelen of het aanroepen van init leidt tot wederzijdse of cyclische situaties tussen pakketten. Dit is moeilijk te traceren, en programma's kunnen zich anders gedragen dan een ontwikkelaar verwacht, vooral bij verborgen afhankelijkheden of het afschermen van de status bij opstart.
Oplossing:
Pakketten in Go worden geïnitieerd in de volgorde die wordt bepaald door hun afhankelijkheden: eerst afhankelijkheden, dan het pakket zelf. Eerst worden package-level variabelen geïnitialiseerd (in de volgorde van verschijnen in het bronbestand), daarna wordt elke init()-functie aangeroepen, als die er is. Meerdere init()-functies kunnen in één bestand worden gedeclareerd. De volgorde van initialisatie tussen bestanden van één pakket is niet gedefinieerd (en dit kan tot fouten leiden).
Codevoorbeeld:
// a.go package main import "fmt" func init() { fmt.Println("init van a.go") } // b.go package main import "fmt" func init() { fmt.Println("init van b.go") }
Het resultaat van de uitvoering van deze init-functies is niet voorspelbaar tussen bestanden in dezelfde directory, maar altijd vóór de main()-functie.
Kernpunten:
Kun je vertrouwen op de volgorde van uitvoering van init-functies in verschillende bestanden van één pakket?
Nee! Go garandeert de volgorde niet tussen init-functies van verschillende bestanden in één pakket. Verwachtingen van een bepaalde volgorde kunnen resulteren in moeilijk te vangen fouten en een uit elkaar vallende bedrijfslogica.
Kunnen globale variabelen niet zijn geïnitialiseerd op het moment van uitvoering van de init-functie?
Nee — alle globale variabelen van het pakket worden strikt in de volgorde van declaratie uitgevoerd vóór alle init-functies van dat pakket. Uitzonderingen zijn alleen kruisinitiatieven tussen pakketten (zie hieronder).
Hoe kun je cyclische afhankelijkheden van init tussen pakketten vermijden?
Go staat geen cyclische import op het niveau van pakketten toe (dit is een compile-time fout), maar je kunt in de val trappen van indirecte initialisatie: A hangt af van B, B van C, en C (via een globale variabele of init) roept code van A aan. In dergelijke gevallen kan een onduidelijke volgorde van het aanroepen van init/globale constructors ontstaan.
In het team zijn de logica en initialisatie van services uitgevoerd in verschillende init-functies van verschillende bestanden. Eén init is afhankelijk van het resultaat van een andere, wat leidt tot willekeurig gedrag tussen builds en bij het draaien op verschillende servers.
Voordelen:
Nadelen:
Alle status en initialisatie worden uitgevoerd met expliciete aanroepen in main(). init-functies worden uitsluitend gebruikt voor traceerfuncties en kleine controles.
Voordelen:
Nadelen: