Teknik materyaller

Go programları, programı hemen çıkmak için os.Exit veya log.Fatal* kullanır (panic kullanmak programdan çıkmak için iyi bir yol değildir, lütfen panic kullanmayın).

Sadece main() içinde os.Exit veya log.Fatal* bir kez çağrılmalıdır. Diğer tüm işlevler hata durumlarını çağıran kodun geriye dönmesi gerekmektedir.

Tavsiye edilmez:

func main() {
  body := readFile(path)
  fmt.Println(body)
}
func readFile(path string) string {
  f, err := os.Open(path)
  if err != nil {
    log.Fatal(err)
  }
  b, err := os.ReadAll(f)
  if err != nil {
    log.Fatal(err)
  }
  return string(b)
}

Tavsiye edilen:

func main() {
  body, err := readFile(path)
  if err != nil {
    log.Fatal(err)
  }
  fmt.Println(body)
}
func readFile(path string) (string, error) {
  f, err := os.Open(path)
  if err != nil {
    return "", err
  }
  b, err := os.ReadAll(f)
  if err != nil {
    return "", err
  }
  return string(b), nil
}

İlke olarak, birden fazla çıkış noktası olan programlar birkaç soruna neden olur:

  • Belirsiz kontrol akışı: Herhangi bir işlev programdan çıkabilir, bu da kontrol akışını anlamayı zorlaştırır.
  • Test etmesi zor: Programdan çıkan işlevler aynı zamanda onları çağıran testleri de sonlandırır. Bu, işlevleri test etmeyi zorlaştırır ve henüz çalıştırılmamış diğer testleri atlamak riskini artırır.
  • Temizliği atlamak: Bir işlev programdan çıktığında, ertelemeli işlev çağrılarını atlar. Bu, önemli temizlik görevlerini atlamak riskini artırır.

Bir defalık çıkış

Mümkünse, main() işlevinizde yalnızca maksimum bir kez os.Exit veya log.Fatal çağrısı olmalıdır. Programın çalışmasını durduran birden fazla hata senaryosu varsa, bu mantığı ayrı bir işlevde yerleştirin ve hatayı oradan geri döndürün. Bu, main() işlevini kısaltacak ve tüm kritik iş mantığını ayrı, test edilebilir bir işlevde yerleştirecektir.

Tavsiye edilmez yaklaşım:

package main
func main() {
  args := os.Args[1:]
  if len(args) != 1 {
    log.Fatal("dosya eksik")
  }
  name := args[0]
  f, err := os.Open(name)
  if err != nil {
    log.Fatal(err)
  }
  defer f.Close()
  // Eğer bu satırdan sonra log.Fatal çağırırsak
  // f.Close çalıştırılacak.
  b, err := os.ReadAll(f)
  if err != nil {
    log.Fatal(err)
  }
  // ...
}

Tavsiye edilen yaklaşım:

package main
func main() {
  if err := run(); err != nil {
    log.Fatal(err)
  }
}
func run() error {
  args := os.Args[1:]
  if len(args) != 1 {
    return errors.New("dosya eksik")
  }
  name := args[0]
  f, err := os.Open(name)
  if err != nil {
    return err
  }
  defer f.Close()
  b, err := os.ReadAll(f)
  if err != nil {
    return err
  }
  // ...
}

Yukarıdaki örnek log.Fatal kullanıyor, ancak bu kılavuz os.Exit veya os.Exit çağrısı yapan herhangi bir kütüphane kodu için de geçerlidir.

func main() {
  if err := run(); err != nil {
    fmt.Fprintln(os.Stderr, err)
    os.Exit(1)
  }
}

İhtiyaca göre run()'un imzasını değiştirebilirsiniz. Örneğin, programınızın belirli bir hata koduyla çıkması gerekiyorsa, run() hata yerine çıkış kodunu geri döndürebilir. Bu ayrıca birim testlerin bu davranışı doğrudan doğrulamasına izin verir.

func main() {
  os.Exit(run(args))
}

func run() (exitCode int) {
  // ...
}

Lütfen bu örneklerde kullanılan run() işlevinin zorunlu olmadığını unutmayın. İsim, imza ve run() işlevinin kurulumu esnektir. Diğer şeylerin yanı sıra şunları yapabilirsiniz:

  • Ayrıştırılmamış komut satırı argümanlarını kabul etmek (örn. run(os.Args[1:]))
  • Komut satırı argümanlarını main() içinde ayrıştırmak ve run'a iletmek
  • Çıkış kodunu özel bir hata türü ile main()'e geri döndürmek
  • İş mantığını farklı bir soyutlama seviyesine yerleştirmek package main

Bu kılavuz, main()'in gerçek çıkış akışından sorumlu bir yerin olmasını gerektirir.