گولانگ کوڈنگ معیار بنیادی رہنمائی

وسائل کو جاری کرنے کے لئے defer کا استعمال کریں

فائلیں اور تالے جیسے وسائل کو جاری کرنے کے لئے defer کا استعمال کریں۔

تجویز نہیں کیا:

p.Lock()
if p.count < 10 {
  p.Unlock()
  return p.count
}

p.count++
newCount := p.count
p.Unlock()

return newCount

// متعدد ریٹرن برانچز ہونے کی صورت میں کمان بھولنا آسان ہوتا ہے

تجویز کیا گیا ہے:

p.Lock()
defer p.Unlock()

if p.count < 10 {
  return p.count
}

p.count++
return p.count

// زیادہ قابل فہم ہے

defer کا اوورہیڈ بہت کم ہوتا ہے، لہذا یہ صرف اس صورت میں ہی اجت avoid کیا جانا چاہئے جب آپ یہ ثابت کر سکتے ہیں کہ فعل کو وقت کا نانو سیکنڈ میں ہے۔ defer کو استعمال کرکے قابل فہمی کو بہتر بنانا قیمتی ہے کیونکہ ان کا استعمال کرنے کی قیمت انہیں ناقابل توجہ ہوتی ہے۔ یہ خصوصی طور پر بڑے میتھڈ کے لئے لاگو ہوتا ہے جو صرف سادہ یادگاری سے زیادہ چیزوں میں مصروف ہوتا ہے، جہاں دوسری حساب کتابوں کی رسائی سے دوسرے حسابوں کی رسائی کی مصروفیت کا صرف defer سے زیادہ ہوتی ہے۔

چینل کا سائز 1 ہو یا بنا سائز ہونا چاہئے

عام طور پر چینلز کا سائز 1 ہونا چاہئے یا بغیر سائز کے ہونا چاہئے۔ چینلز کا پہلے سے ترتیب نہیں ہوتا، اور صفر کا سائز ہوتا ہے۔ کسی بھی دوسرے سائز کو سختی سے جائزہ لینا چاہئے۔ ہمیں سوچنا چاہئے کہ سائز کیسے تعین کرنا ہے، سوچنا چاہئے کہ چینل کو لکھنے سے کیسے روکا جا سکتا ہے اور جب بلاک ہوتا ہے تو کیا تبدیلیاں نظامی منطق میں پیدا ہوتی ہیں۔

تجویز نہیں کیا:

// ہر صورت کے لئے کافی ہونا چاہئے!
c := make(chan int, 64)

تجویز کیا گیا ہے:

// سائز: 1
c := make(chan int, 1) // یا
// بغیر بفر شدہ چینل، سائز صفر ہے
c := make(chan int)

انم قدر 1 سے شروع ہونا چاہئے

اگر بنیادی طور پر متغیرات کی پیش فرضی قیمت صفر ہے تو، گولانگ میں انمز کے درآمد کا معیاری طریقہ ہوتا ہے کہ ایک کسٹم ٹائپ اور موقع جو اٹا استعمال کرے کو نروائکرا کریں۔ بنیادی طور پر متغیرات کی پیش فرضی قیمت صفر ہونے کی وجہ سے، انم کو عام طور پر غیر صفر قیمت سے شروع ہونا چاہئے۔

تجویز نہیں کیا:

type Operation int

const (
  Add Operation = iota
  Subtract
  Multiply
)

// Add=0, Subtract=1, Multiply=2 

تجویز کیا گیا ہے:

type Operation int

const (
  Add Operation = iota + 1
  Subtract
  Multiply
)

// Add=1, Subtract=2, Multiply=3 

کچھ صورتوں میں صفر قیمت کا استعمال کرنا مناسب ہوتا ہے (صفر سے آغاز ہونے والے انمز)، مثلاً، جب صفر قیمت بہترین پیش فرضی رویہ ہوتی ہے۔

type LogOutput int

const (
  LogToStdout LogOutput = iota
  LogToFile
  LogToRemote
)

// LogToStdout=0, LogToFile=1, LogToRemote=2 

ایٹومک کا استعمال

بنیادی قسمیں (int32, int64 وغیرہ) پر عام طور پر sync/atomic پیک کے ایٹومک عمل کرنے کے لئے استعمال کریں کیونکہ متغیرات سے پڑھنے یا ان کو تبدیل کرنے کے لئے ایٹومک عمل کرنا آسان ہوتا ہے۔

go.uber.org/atomic ان عمل کو چھپا کر ان کی قسم کی سلامتی کو شامل کرتا ہے۔ علاوہ ازیچہ، یہ ایک آسان atomic.Bool قسم شامل کرتا ہے۔

تجویز نہیں کیا گیا روش:

type foo struct {
  running int32  // atomic
}

func (f* foo) start() {
  if atomic.SwapInt32(&f.running, 1) == 1 {
     // already running…
     return
  }
  // start the Foo
}

func (f *foo) isRunning() bool {
  return f.running == 1  // race!
}

تجویز کیا گیا روش:

type foo struct {
  running atomic.Bool
}

func (f *foo) start() {
  if f.running.Swap(true) {
     // already running…
     return
  }
  // start the Foo
}

func (f *foo) isRunning() bool {
  return f.running.Load()
}

تغیر‌پذیر گلوبل متغیرات سے بچیں

تبدیلی کے گلوبل متغیرات سے بچنے کے لئے dependency injection کا طریقہ استعمال کریں. یہ تقریباً تمام قسم کے فنکشن پوائنٹر اور دیگر قیمت کی قسموں کے لئے درست ہے۔

غیر موصول انداز 1:

// sign.go
var _timeNow = time.Now
func sign(msg string) string {
  now := _timeNow()
  return signWithTime(msg, now)
}

موصول انداز 1:

// sign.go
type signer struct {
  now func() time.Time
}
func newSigner() *signer {
  return &signer{
    now: time.Now,
  }
}
func (s *signer) Sign(msg string) string {
  now := s.now()
  return signWithTime(msg, now)
}

غیر موصول انداز 2:

// sign_test.go
func TestSign(t *testing.T) {
  oldTimeNow := _timeNow
  _timeNow = func() time.Time {
    return someFixedTime
  }
  defer func() { _timeNow = oldTimeNow }()
  assert.Equal(t, want, sign(give))
}

موصول انداز 2:

// sign_test.go
func TestSigner(t *testing.T) {
  s := newSigner()
  s.now = func() time.Time {
    return someFixedTime
  }
  assert.Equal(t, want, s.Sign(give))
}

پہلے سے طے کردہ شناختوں کا استعمال نہ کریں

Go Language Specification میں کئی پہلے سے طے کردہ شناختوں کا استعمال نہ کرنے کا تفصیلی بیان ہے جو Go منصوبوں میں استعمال نہ کرنے کے لئے مشورہ کرتا ہے۔ پہلے سے طے کردہ شناخت مختلف سیاقوں میں ناموں کے طور پر دوبارہ استعمال نہ کیا جائے چاہئے، کیونکہ ایسا کرنا موجودہ حدود (یا کسی بھی نسٹڈ حدود) میں اصل شناختوں کو چھپا سکتا ہے جس سے خود میں کوڈ کی الجھن کا باعث بن سکتا ہے۔ بہترین صورت میں، کمپائلر تشکیل دے گا لیکن سب سے برہانے میں، ایسا کوڈ ممکن ہے جو پتھر پر کی مشکل سے برابر ہو سکتا ہے۔

غیر موصول تجربہ 1:

var error string
// `error` built-in شناخت کو ضمنی طور پر چھپا دیتا ہے

// یا

func handleErrorMessage(error string) {
    // `error` built-in شناخت کو ضمنی طور پر چھپا دیتا ہے
}

موصول تجربہ 1:

var errorMessage string
// `error` اب غیر چھپا شدہ built-in شناخت کو اشارہ کرتا ہے

// یا

func handleErrorMessage(msg string) {
    // `error` اب غیر چھپا شدہ built-in شناخت کو اشارہ کرتا ہے
}

غیر موصول تجربہ 2:

type Foo struct {
    // یہ فیلڈ تقنی طور پر چھپا نہیں کرتے، پہلے سے طے کردہ `error` یا `string` اب ستریں امنا میں ابھور ہو جاتے ہیں۔
    error  error
    string string
}

func (f Foo) Error() error {
    // `error` اور `f.error` بصری طور پر مشابہ نظر آتے ہیں
    return f.error
}

func (f Foo) String() string {
    // `string` اور `f.string` بصری طور پر مشابہ نظر آتے ہیں
    return f.string
}

موصول تجربہ 2:

type Foo struct {
    // `error` اور `string` اب صریح ہیں۔
    err error
    str string
}

func (f Foo) Error() error {
    return f.err
}

func (f Foo) String() string {
    return f.str
}

نوٹ: کمپائلر پہلے سے طے کردہ شناخت کا استعمال کرتے وقت خطا نہیں پیدا کرے گا، لیکن go vet جیسے اوزار منفرد مسائل کو درست طریقے سے پیش کرے گا۔

init() کا استعمال اجتناب کریں

جتنی ممکن ہو، init() کا استعمال اجتناب کرنے کی کوشش کریں۔ جب init() سے باز نہیں بچا جا سکتا ہو یا اسے ترجیح دی جائے، تو کوڈ کو درج ذیل کرنے کی کوشش کرنی چاہئے:

  1. برنامج کے ماحول یا کال کے بغیر مکمل ہونے کی یقینی کیجئے۔
  2. دوسرے init() فنکشن کے ترتیب یا اثرات پر انحصار سے بچیں۔ جبکہ init() کی ترتیب صاف ہے، لیکن کوڈ تبدیل ہوسکتا ہے، لہذا init() فنکشنز کے درمیان رشتے کوڈ کو کمزور اور خرابی پذیر بنا سکتے ہیں۔
  3. عام یا ماحولی حالات جیسے مشین کی معلومات، ماحولی متغیرات، کام کرنے والے ڈائرکٹریز، برنامج پیرامیٹر/ان پٹ کا رسائی یا ترتیب دینے، سے بچیں۔
  4. فائل سسٹم، نیٹ ورکنگ، اور سسٹم کالز جیسی I/O سے بچیں۔

ان شرائط کو پورا نہ کرنے والا کوڈ main() کال (یا برنامج کے عمر کے دوسرے حصے میں) کا حصہ ہو سکتا ہے یا main() کا حصہ بنایا جا سکتا ہے۔ خاص طور پر، دیگر برامد برامد برنامج کے استعمال کے لئے مخصوص کتب خصوصی طور پر مکمل ہونے کی بجائے "init جادو" کرنے کی بجائے خصوصی توجہ دینی چاہئے۔

غیر موصول روایت 1:

type Foo struct {
    // ...
}
var _defaultFoo Foo
func init() {
    _defaultFoo = Foo{
        // ...
    }
}

موصول روایت 1:

var _defaultFoo = Foo{
    // ...
}
// یا، بہتر ٹیسٹ کے لئے:
var _defaultFoo = defaultFoo()
func defaultFoo() Foo {
    return Foo{
        // ...
    }
}

غیر موصول روایت 2:

type Config struct {
    // ...
}
var _config Config
func init() {
    // براہِ راست: موجودہ ڈائریکٹری پر مبنی
    cwd, _ := os.Getwd()
    // براہ کرم: I/O
    raw, _ := os.ReadFile(
        path.Join(cwd, "config", "config.yaml"),
    )
    yaml.Unmarshal(raw, &_config)
}

موصول روایت 2:

type Config struct {
    // ...
}
func loadConfig() Config {
    cwd, err := os.Getwd()
    // خطا کا حل کریں
    raw, err := os.ReadFile(
        path.Join(cwd, "config", "config.yaml"),
    )
    // خطا کا حل کریں
    var config Config
    yaml.Unmarshal(raw, &config)
    return config
}

اوپر دی گئی سوچ کے مطابق، کچھ مواقع میں، init() زیادہ ترجیحی یا ضروری ہو سکتا ہے، جیسے:

  • ایک پیچیدہ اظہار کی میں تمثیل کے طور پر نمایاں نہیں کیا جا سکتا ہے۔
  • قابل داخل ہوکس، جیسے database/sql، قسم کی رجسٹریز وغیرہ۔

ایپینڈ کرنے کے وقت سلائس کی کیپیسٹی کا تفضل کریں

سلائس کو اضافہ کرنے سے پہلے make() کے لئے کیپیسٹی کی قدر کو ترجیح دیں۔

غیر موصول روایت:

for n := 0; n < b.N; n++ {
  data := make([]int, 0)
  for k := 0; k < size; k++{
    data = append(data, k)
  }
}

موصول روایت:

for n := 0; n < b.N; n++ {
  data := make([]int, 0, size)
  for k := 0; k < size; k++{
    data = append(data, k)
  }
}

سٹرکٹ سیریلائزیشن میں فیلڈ ٹیگس کا استمعال

جب JSON، YAML یا کسی دوسرے فارمیٹ میں سیریلائز کر رہے ہوں، تو موجودہ ٹیگز کو اشاروں کے طور پر استعمال کیجئے۔

غیر موصول روایت:

type Stock struct {
  Price int
  Name  string
}
bytes, err := json.Marshal(Stock{
  Price: 137,
  Name:  "UBER",
})

موصول روایت:

type Stock struct {
  Price int    `json:"price"`
  Name  string `json:"name"`
  // نام کو رینیم کرنا سیکور ہے۔
}
bytes, err := json.Marshal(Stock{
  Price: 137,
  Name:  "UBER",
})

نظری طور پر، ایک سٹرکچر کی سیریلائزیشن فارم ایک معاہدہ ہے۔ سٹرکچر کی سیریلائزیشن فارم میں تبدیلیاں (فیلڈ کے نام شامل ہیں) یہ معاہدہ توڑ دیں گی۔ ٹیگز میں فیلڈ کے نام معاہدہ کو واضع بناتے ہیں اور بھی کسی شدت سے اس طرح کے تباہیوں سے روکتے ہیں جو تدوین یا فیلڈز کے نام کو رینیم کرنے کے ذریعے ناسازیآوارانہ معاہدہ کے انحصارات کی طرف سے ہوسکتی ہیں۔